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