Ls with spaces + variables

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

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

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 .[!.]* "[email protected]"; [ -e "$1" ] || shift
set ..?* "[email protected]"; [ -e "$1" ] || shift
for x; do …  # short for for x in "[email protected]"; 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.

Solution 2

This code will do you want you want-

echo *

What exactly are you trying to do here?

You can also do

echo $var

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

Solution 3

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

fs=( )
echo ${fs[@]}
for file in `ls`
        echo $file
echo $fs
echo "All files/dirs: "${fs[@]}

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from or, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply