Don't parse the output of ls
.
The way to list all the files in a directory in the shell is simply *
.
for f in *; do …
In shells with array support (bash, ksh, zsh), you can directly assign the file list to an array variable: fs=(*)
This omits dot files, which goes against your use of ls -A
. In bash, set the dotglob
option first to include dot files, and set nullglob
to have an empty array if the current directory is empty:
shopt -s dotglob nullglob
fs=(*)
In ksh, use FIGNORE=".?(.)"; fs=(~(N)*)
. In zsh, use the D
and N
glob qualifiers: fs=(*(DN))
. In other shells, this is more difficult; your best bet is to include each of the patterns *
(non-dot files), .[!.]*
(single-dot files, not including .
and double-dot files) and ..?*
(double-dot files, not including ..
itself), and check each for emptiness.
set -- *; [ -e "$1" ] || shift
set .[!.]* "$@"; [ -e "$1" ] || shift
set ..?* "$@"; [ -e "$1" ] || shift
for x; do … # short for for x in "$@"; do …
I'd better explain what was going wrong in your attempt, too. The main problem is that each side of a pipe runs in a subprocess, so the assignments to fs
in the loop are taking place in a subprocess and never passed on to the parent process. (This is the case in most shells, including bash; the two exceptions are ATT ksh and zsh, where the right-hand side of a pipeline runs in the parent shell.) You can observe this by launching an external subprocess and arranging for it to print its parent's process ID¹´²:
sh -c 'echo parent: $PPID'
{ sh -c 'echo left: $PPID >/dev/tty'; echo $? >/dev/null; } |
{ sh -c 'echo right: $PPID >/dev/tty'; echo $? >/dev/null; }
In addition, your code had two reliability problems:
For those times when you do need to parse lines and use the result, put the whole data processing in a block.
producer … | {
while IFS= read -r line; do
…
done
consumer
}
¹
Note that $$
wouldn't show anything: it's the process ID of the main shell process, it doesn't change in subshells.
²
In some bash versions, if you just call sh
on a side of the pipe, you might see the same process ID, because bash optimizes a call to an external process. The fluff with the braces and echo $?
defeat this optimization.
sudo ls -A1
to grab the files, how would I do that with the *? – Tyilo Jul 19 '11 at 21:16sudo bash -c 'shopt -s nullglob dotglob; printf "%s\\0" *' | while IFS= read -r -d "" filename; do …
seems to work (using a null character as the separator, this way all file names work). – Gilles 'SO- stop being evil' Jul 19 '11 at 21:51while IFS= read -r -d "" filename; do f+=("$filename"); done < <(shopt -s nullglob dotglob; printf "%s\\0" *)
-- this also keeps the shopt in a subshell. – glenn jackman Jul 19 '11 at 22:14