strings returned by find gets separated in the mid

2019-08-05 05:34发布

问题:

I'm new to shell (bash) script. On terminal, find behaves as:

find . -name 'Ado*'
./Adobe ReaderScreenSnapz001.jpg
./Adobe ReaderScreenSnapz002.jpg

However, my shell script seems to separate the file name by the space after "Adobe".

Code:

#!/bin/sh
i=1
for base_name in `find . -name "Ado*"`
do
echo $base_name
 :
i=$((i+1))
done

It returns something like:

./Adobe
ReaderScreenSnapz001.jpg
 :

What's wrong where?

回答1:

Most of the answers so far have used find ... | while read ... which works, but the while loop runs in a subshell (because it's part of a pipeline), and so your count of files ($i) is lost when the subshell exits. If you're using bash (i.e. your shebang must be #!/bin/bash, not #!/bin/sh), you can use its process substitution feature to avoid this problem:

i=1
while IFS= read -r -u3 -d '' base_name; do
    echo "$base_name"
    :
    i=$((i+1))
done 3< <(find . -name "Ado*" -print0)

I've also added some other tricks here to make this more robust: I set IFS to null (just for the read command) so it won't trim leading or trailing whitespace from filenames; I use read's -r option so it won't do funny things with backslashes in filenames; I use find's -print0 and read's -d '' to use null bytes as the file delimiter, so it won't even be confused by linefeeds in filenames; I use double-quotes around $base_name inside the loop, so it won't get split on whitespace there either (not important for echo, but it'll matter if you actually try to use the filenames as filenames); finally, I pass the file list via fd #3 instead of #0 (stdin) with 3< and read's -u3 option, so in case anything inside the loop reads from stdin it won't accidentally inhale a pile of filenames.



回答2:

find . -name 'Ado*'
./Adobe ReaderScreenSnapz001.jpg
./Adobe ReaderScreenSnapz002.jpg

The find result contains white-spaces.
So the following for loop iterate over 4 items.

You can change the script to:

i=1
find . -name 'Ado*' | while read base_name
do
    echo $base_name
    :
    i=$((i+1))
done


回答3:

Your shell replaces for base_name in $(find . -name "Ado*") by
for base_name in ./Adobe ReaderScreenSnapz001.jpg ./Adobe ReaderScreenSnapz002.jpg

Therefore, for sees four words:
- ./Adobe
- ReaderScreenSnapz001.jpg
- ./Adobe
- ReaderScreenSnapz002.jpg

My advice: replace for base_name in by while read base_name

#!/bin/sh
i=1
find . -name "Ado*" |
while read base_name
do
echo $base_name
 :
i=$((i+1))
done

read reads line after line and stops the while loop at the end of its input (find).
read stores each line in base_name variable.



回答4:

There is nothing wrong in your shell script.

When you call a program between back quotes the command is executed in a forked process, and the result is returned to the main program to be lexed and parsed. And as a string the result is separated into several token.

You should use read builtin like that

#! /bin/sh
i=0
find . -name 'Ado*' | while read result
do
echo $result
i=$((i+1))
done


标签: bash shell