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):

  1. Use find and its -exec option, the {} will be replaced by each file or directory found.

    find . -exec cp {} directory/
    
  2. Pipe find's results to xargs as null separated strings (-print0), tell xargs to read null separated (-0) and tell it to replace {} with each file/directory name (-I {}).

    find . -print0 | xargs -0 -I {} cp {} directory/
    
  3. Use the shell alone, to match dotfiles as well, activate dotglob

     shopt -s dotglob
     for i in *; do cp -v "$i" directory/; done
    
  4. Combine the power of find and the versatility of the shell

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