Take the 2-minute tour ×

I want to do something like this, but it doesn't save the variable after the piping ends:

fs=( )
echo ${fs[@]}
ls -A1 |
while read f
    echo ${fs[@]}
    fs+=( "$f" )
    echo ${fs[@]}
echo "All files/dirs: "${fs[@]}

With files 1, 2 and 3 in the current dir I get this output:

# Output:

1 2
1 2
1 2 3
All files/dirs: 

How do I keep the fs variable's value, after the piping ends?


  • Can't use *, becuase i need hidden files
  • Can't just use fs=($(ls)), while sometimes the file/dir names will have spaces in them
share|improve this question

3 Answers 3

up vote 8 down vote accepted

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

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

¹ 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.

share|improve this answer
One last thing: if I normally wanted to use sudo ls -A1 to grab the files, how would I do that with the *? –  Tyilo Jul 19 '11 at 21:16
I want to list the files in a partition's .Trashes folder (/Volumes/External HD/.Trashes/), which requires root priviledges. –  Tyilo Jul 19 '11 at 21:30
@Tyilo: Hmm, tricky. Bash doesn't support null characters in general, but sudo 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 Jul 19 '11 at 21:51
The way to remove the pipeline and hence remove the subshell (and keep the variable in the current shell) is to use process substitution: while 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

Do you really need the pipe? You can easily do it with for:

fs=( )
echo ${fs[@]}
for file in `ls`
        echo $file
echo $fs
share|improve this answer
`ls` will choke horribly on file names containing whitespace, unprintable characters, or shell wildcards. Don't do this. –  Gilles Jul 19 '11 at 20:45

This code will do you want you want-

echo *

What exactly are you trying to do here? (Sorry, not enough rep to leave just a comment)
You can also do

echo $var

If you need more than that, I recommend you go to stackoverflow.

share|improve this answer
This doesn't appear to answer the question at all, if I'm understanding it correctly. In any case, you shouldn't post answers just because you lack the rep for a comment, and definitely don't tell people to go to SO for their completely on-topic questions –  Michael Mrozek Jul 19 '11 at 20:49
It is a potential answer but the asker rulled it out already based on needing more features than just globbing. @Michael's other points stand, don't post question clarification comments in answers and don't send people off-site for on-topic issues. –  Caleb Jul 20 '11 at 15:54
Thats all fine and valid, I just don't think it should've been deleted. Furthermore, I posted this answer -before- he updated his post. –  user606723 Jul 20 '11 at 16:17

Your Answer


