How do I make the shell to recognize the file names returned by a `ls -A` command, and these names contain spaces?
This is the part of the script I am using to rename the files returned by ls -A
:
for FILE in `ls -A`
do
ext=${FILE##*.}
NUM=$(($NUM+1))
NEWFILE=`echo $2_$NUM.$ext | sed 's/ /_/g'`
mv "$FILE" "$NEWFILE"
done
But can not find names with space! The $2 parameter can not be the cause of the error why I ever step a name without a space as a parameter, and this script takes that name and rename all files in the folder so to let them numbered and does not modify the file extension. Unfortunately it does not rename files with spaces in the name. Can someone help me?
for
iterates over words, words are delimited by whitespace. You should not iterate over the output of ls
, you should use * .*
:
for file in * .* ; do
if [[ $file = . || $file = .. ]] ; then
continue
fi
# ...
done
You can include hidden files ('dot files') in the bash '*' shell glob by setting the dotglob
shell option
shopt -s dotglob
for file in *
do
echo "$file"
done
e.g. for a directory that contains file
, file with spaces
and .hidden file
(the last of which is hidden and has a space) this produces
file
file with spaces
.hidden file
You may want to add the nullglob
option as well to prevent an error condition in the case that the directory is empty - see the excellent BashFAQ/004 . Remember to quote the variable "$file"
and also it's best practice not to use all-caps for your variable names.
ls -A
for me writes multiple filenames on one line, separated by white space. If you tried adding -1
as in ls -A1
that would output one filename per line, and might work better for you.
I've run into the same problems with spaces in filename, espeically when using find, but separating names with a null character handles spaces & newlines in filenames:find (...) -print0 | xargs -0 (do stuff here)
But, if you just want to rename files you might consider man rename
it can do things like:
For example, to rename all files matching "*.bak" to strip the extension,
you might say
rename 's/\.bak$//' *.bak
To translate uppercase names to lower, you'd use
rename 'y/A-Z/a-z/' *
Or for a gui solution for a few directories Thunar has a nice Rename Multiple Files interface that can do numbering how you're describing too. I just tried some filenames with spaces combined with find to Thunar and it seems to work:find . -type f -print0 | xargs -0 thunar -B
In case you haven't figured out yet, parsing ls
is a Bad Idea®. Unless you can know that your file names will always be sane (you usually can't), you need to be able to deal with file names containing:
- spaces and tabs
- consecutive spaces or tabs
- newlines (
\n
) - carriage returns (
\r
) - backslashes (
\
)
All of the above are allowed by the Linux kernel (and are guaranteed to drive your sysadmin mad). The following two methods can deal with any combination of the above. I am using cp file directory/
as an example command):
-
Use
find
and its-exec
option, the{}
will be replaced by each file or directory found.find . -exec cp {} directory/
-
Pipe find's results to
xargs
as null separated strings (-print0
), tellxargs
to read null separated (-0
) and tell it to replace{}
with each file/directory name (-I {}
).find . -print0 | xargs -0 -I {} cp {} directory/
-
Use the shell alone, to match dotfiles as well, activate
dotglob
shopt -s dotglob for i in *; do cp -v "$i" directory/; done
-
Combine the power of
find
and the versatility of the shellfind . -print0 | while IFS= read -r -d '' i; do cp "$i" directory/; done
The
IFS=
disables splitting at spaces, the-r
disables backslash escapes (allows backslashes to be treated literally), the-d ''
sets the record separator (lines if you like) to null.