Bash variables with space (directories)

6

1

I'm trying to echo all of the files in /foo while preserving the spaces within listed directories. Currently, a directory titled "bar1 bar2" will echo out as /path/to/foo/bar1/ then /bar2 then file.txt all on new lines. I need it to echo /path/to/foo/bar1 bar2/file.txt or /path/to/foo/bar1\ bar2/file.txt.

for file in $(find /path/to/foo/ -type f); do
  if [[ ... ]]; then
    echo "${file}"
  fi
done

Also tried:

for file in $(find /path/to/foo/ -type f | sed 's/ /\ /g'); do
  if [[ ... ]]; then
    echo "${file}"
    echo "$file" # variations i've tried
    echo $file   # variations i've tried
    echo ${file}
    otherCommand "${file}"
  fi
done

super-frustrated

Posted 2017-08-24T05:01:31.263

Reputation: 85

2Here's a rule for Linux Life: If you have trouble doing it in Bash, stop doing it in Bash and use something else.... Bash is a terrible language. Use Perl or something. – Ben – 2017-08-24T10:13:01.733

Also see: https://superuser.com/a/117600/334516

– muru – 2017-08-24T11:10:41.533

Also see:   Why is looping over find's output bad practice?

– G-Man Says 'Reinstate Monica' – 2017-09-12T05:30:58.943

Answers

15

The problem is not with $file, it's with your usage of $(find…).

Both regular variables and $(…) process substitutions are subject to exactly the same kind of word-splitting. If you put $(find…) in double quotes, you get a single word with all output. If you don't, it will be split up at any whitespace – not just newlines or some other magical boundary that you probably expected.

An important part of the above is that backslash escapes are not processed when expanding the variable. It's just split at spaces and that's it.

(In other words, quoting $file didn't help because it never had the correct value in the first place!)

If you want to process all files recursively, your options are:

  1. Read output from find in another way:

    find … -print | while read -r file; do
        echo "Got $file"
    done
    

    (Side note: File names may technically include newlines as well, so you might want to guard against that, as rare as it is:

    find … -print0 | while read -r -d "" file; do
        echo "Got $file"
    done
    
  2. For small amounts of files, use bash's extended wildcards:

    shopt -s globstar
    for file in /path/to/foo/**; do
        echo "Got $file"
    done
    

With large directories, the advantage of find | while read is that it's streamed – your script will process results as they come. Meanwhile, both $(find) and wildcards have to collect everything into memory first, and only then return the (possibly massive) results.

On the other hand, using a pipe affects the entire while loop (not just the read command), so if you want to run anything that wants to read keyboard input, you have to manually provide it with the original stdin, e.g. vim "$file" < /dev/tty.

user1686

Posted 2017-08-24T05:01:31.263

Reputation: 283 655

1Wow; your response time was incredible. Thank you so much. – super-frustrated – 2017-08-24T05:49:37.273

@super-frustrated Then you should mark it "accepted" – xenoid – 2017-08-24T06:44:12.627

Apologies. I don't use stackexchanges often. Marked it. – super-frustrated – 2017-08-24T06:49:49.460

3

It's best to use ... | IFS= read ...; without that, read will remove leading and trailing whitespace (which is rare, but perfectly legal in filenames). Also, note that the pipe makes the loop run in s subshell, so any variables set in it will not be available outside the loop. See BashFAQ #20: "How can I find and safely handle file names containing newlines, spaces or both?" for more tips and tricks.

– Gordon Davisson – 2017-08-25T05:36:00.417

Newlines aren't that rare. I definitely know better, but I still accidentally ended up with some by copying text from a webpage and pasting it into a file name. What you see is definitely not always what you get! I have not yet seen a file name with leading or trailing spaces in it. That could take quite awhile to debug if the variables aren't properly defended. It's almost totally invisible. – Joe – 2017-08-29T06:16:50.187

1Leading spaces should be fairly obvious if you do ls or find -print. – G-Man Says 'Reinstate Monica' – 2017-09-12T05:28:07.680