help-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Loop with files coming through sorted by name


From: Seth David Schoen
Subject: Re: Loop with files coming through sorted by name
Date: Fri, 15 Oct 2021 19:34:56 -0700

tolugboji via writes:

> I have a list of image files and want to pass them in a loop with files 
> coming through sorted by name.
> 
> Have seen this approach
> 
> for
> 
> filename
> 
> in
> 
> `ls | sort -n`
> 
> Are backticks still a good way to do this, as currently things are shifting 
> towards $().

Backticks are no longer considered good shell style and $() is always
preferred nowadays.

Apart from that, there's the issue about filenames that may contain
spaces.  While Unix style discourages spaces in filenames (partly
because of the popularity of these loops, no doubt!), they are
completely possible and permissible at the OS level and not necessarily
uncommon.  If you do the bare

for filename in $(ls | sort -n); do

form, then filenames with spaces will be handled incorrectly.  Consider

$ touch "uh oh"
$ touch "this won't work"
$ for i in $(ls | sort); do cat $i; done
cat: this: No such file or directory
cat: "won't": No such file or directory
cat: work: No such file or directory
cat: uh: No such file or directory
cat: oh: No such file or directory

There are tricks available involving changing the bash IFS (input field
separator) variable, as well as the form

for filename in "$(ls | sort -n)"

but a further thing to ponder is that newline characters are also
officially valid in filenames (!).  If a filename contained a newline,
it would still break in this format -- the sort command itself would
consider the filename to be multiple lines.

Shell globbing is safe in for loops (in the sense that "for i in *" works
regardless of what kind of whitespace or control characters are in the
filenames), but it still doesn't have the interaction that you want
with the sort command, because it will still consider a newline to be
a delimiter between records.

Typically people will suggest instead using the find command, either
with the -exec flag or with the -print0 flag.  This is extensively
described in

https://stackoverflow.com/questions/9612090/how-to-loop-through-file-names-returned-by-find

A disadvantage there is that the command run by the -exec can only be
a single command, not a bunch of shell scripting or even a pipeline.

A cool thing with -print0 is that you can, in fact, combine it nicely
with sort using the -z option, like

find . -type f -name '*.ogg' -print0 | sort -z -n | xargs -r -0 ogg123

will work to sort .ogg files in numeric order and them play them back,
regardless of whitespace in the filenames.  There is also grep -z,
which works with the null-terminated lines produced by -print0.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]