[SOLVED] How to handle files with spaces in their names in bash?
ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
How to handle files with spaces in their names in bash?
Hi all,
*I'm using Ubuntu 10.10
My issue is I can't handle the files with spaces in their name, I've donde the below script to print each file
found inside folder and subbfolders with "find".
I would like to "ls" to each file found with its complete path and with its basename too.
Code:
files=$(find . -type f)
for each in "$files"
do
ls -l "$each" # 1rst option I've tried to list with full path
ls -l "$(/bin/echo "$each")" # 2nd option I've tried to list with full path
ls -l "$(/bin/echo $(basename "$each"))" # 1nd option I've tried to list with it basename
ls -l "$(/bin/echo "$(basename "$each")")" # 2nd option I've tried to list with it basename
done
All of the "ls -l" options above I've tried, give me errors (ls: cannot access...) if there is at least one file within $files list, with spaces in the filename.
How can I list "ls -l" in both cases (with full path and with basename) when there are files with spaces in their name?
The safe method is to use EOS (zero) as a separator, and read them to an array:
Code:
#!/bin/bash
find . -type f -print0 | (
while read -d "" FILE ; do FILES=("${FILES[@]}" "$FILE") ; done
echo "Found ${#FILES[@]} files:"
for FILE in "${FILES[@]}" ; do
echo "'$FILE'"
done
)
Remember to use doublequotes around the file names when using them as a parameter. For example, to list all the files, you can use
Code:
#!/bin/bash
find . -type f -print0 | (
while read -d "" FILE ; do FILES=("${FILES[@]}" "$FILE") ; done
ls -laF "${FILES[@]}"
)
or
Code:
#!/bin/bash
find . -type f -print0 | (
while read -d "" FILE ; do FILES=("${FILES[@]}" "$FILE") ; done
for FILE in "${FILES[@]}" ; do
ls -laF "$FILE"
done
)
which are otherwise equivalent, except the latter one uses a loop, listing each file separately, while the previous one gives the ls command all the file names in one go.
The safe method is to use EOS (zero) as a separator, and read them to an array:
Code:
#!/bin/bash
find . -type f -print0 | (
while read -d "" FILE ; do FILES=("${FILES[@]}" "$FILE") ; done
echo "Found ${#FILES[@]} files:"
for FILE in "${FILES[@]}" ; do
echo "'$FILE'"
done
)
Hi Nominal_A,
Thanks for your reply. It works nice!
I understand better now why you introduced the option print0.
Code:
-print0 True; print the full file name on the standard output, followed by a null character (instead of the newline character that '-print' uses). This allows file names that contain newlines or other types of white space to be correctly interpreted by programs that process the find output. This option corresponds to the '-0' option of xargs.
Now I have 2 questions:
1-)May you explain how the array is loaded with this syntax? How it works?
Code:
FILES=("${FILES[@]}" "$FILE")
2-) How can be introduce the basename option?
I've tried:
1-)May you explain how the array is loaded with this syntax? How it works?
Code:
FILES=("${FILES[@]}" "$FILE")
The statement replaces the list in variable FILES with another list. The new list contains each and every one of the old list items, plus one new one, $FILE. For Bash, "${list[@]}" expands to each of the list elements as a separate token. Quotes are important, since Bash uses them to understand which ones are supposed to be separate tokens.
Quote:
Originally Posted by cgcamal
2-) How can be introduce the basename option?
You will need to loop thorough the FILES array, and apply basename separately to each one:
Code:
for FILE in "${FILES[@]}" ; do
BASE="`basename "$FILE"`"
ls -laF "$BASE"
done
If you want, you can also create a parallel array containing only the base names:
Code:
BASENAMES=()
for FILE in "${FILES[@]}" ; do
BASENAME=("${BASENAMES[@]}" "`basename "$FILE"`")
done
after which you can run any command and supply all the base names to it:
Code:
ls -laF "${BASENAMES[@]}"
If you have multiple parallel arrays like FILES and BASENAMES above, and you want to loop thorough them, you'll want to use this:
Code:
for ((I=0; I<${#FILES[@]}; I++)) ; do
FILE="${FILES[I]}"
BASE="${BASENAMES[I]}"
# Do something with $FILE and $BASE ...
done
Note that ${#FILES[@]} evaluates to the number of items in FILES, and in Bash arrays, indexes start from zero.
Also, you can append to a bash array using += notation as well:
Code:
FILES+=("$FILE")
I think that only works in Bash 3 and newer. I frequently use systems with an older version of Bash, so I always try to make my scripts compatible.
Unfortunately, I cannot find reliable documentation for older versions of Bash -- even Waybackmachine is glitching right now --, so I'm not sure. If you find a good source for previous bash manuals, I'd appreciate it.
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.