Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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
do
    echo ${fs[@]}
    fs+=( "$f" )
    echo ${fs[@]}
done
echo "All files/dirs: "${fs[@]}

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

# Output:


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

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

Update:

  • 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
add comment

3 Answers

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

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
1  
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
add comment

This code will do you want you want-

#!/bin/sh
echo *

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

var=*
echo $var

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

share|improve this answer
3  
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
add comment

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

fs=( )
echo ${fs[@]}
for file in `ls`
do
        echo $file
        fs+=$file
done
echo $fs
echo "All files/dirs: "${fs[@]}
share|improve this answer
2  
`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
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.