There are several problems with your script.
for x in 'ls'
You're iterating over a list containing one word: ls
. You probably meant to write `ls`
(with backticks), to parse the output of ls
. Don't parse the output of ls
: this won't work if the file names contain special characters such as spaces. The shell already has a built-in way of listing the files in a directory: wildcards. So write for x in *
.
if [ ! -f $x ]; then
This won't work if the file name contains whitespace and other special characters, because when you write $x
outside quotes, the result is split into separate words (and each word is interpreted as a wildcard pattern, to boot). To avoid this effect, put $x
in double quotes: if [ ! -f "$x" ]; then
. You can remember complex rules, or just always use double quotes around variable substitutions.
Several other lines are broken due to the lack of double quotes. Just put them around every variable substitutions.
lc = `echo $x | tr '[A-Z]' '[a-z]'`
This won't work due to the spaces around the equal sign: this line executes the lc
command, passing it =
as its first argument (and other arguments). An assignment in the shell must have no whitespace around the =
sign.
You don't need brackets around the arguments of tr
. Here the command happens to work because you're saying to replace [
by [
and ]
by ]
in addition to the lowercasing.
I recommend using dollar-parentheses $(…)
rather than backticks `…`
for command substitution. They're equivalent, except that backticks have complex rules when it comes to quoting things inside it, whereas $(…)
has a very intuitive syntax. All in all, that command should be: lc=$(echo "$x" | tr A-Z a-z)
find -name "* *" -type f | rename 's/ /-/g'
Your error message shows that you have the rename
command from the util-linux package and not the Perl script found on Debian and derivatives (including Ubuntu). The syntax you used only makes sense with the Perl script.
If you're going to use the Perl script, it can change the case to lowercase, so you don't need to do that beforehand. And you don't need to call find
unless you want to rename files in subdirectories too (which your first part doesn't handle). If you want to rename all files in the current directory, you can just write:
prename 'tr/A-Z /a-z-/' -- *
If you only want to rename regular files (but not subdirectories, symbolic links, etc.), use find
:
find . -type f -maxdepth 1 -exec prename 'tr/A-Z /a-z-/' {} +
If you want to act on files in subdirectories as well, and the directory names may contain spaces or uppercase letters, you have to take care to leave them alone. Since you're on Linux, you can use -execdir
to call prename
with an argument that doesn't contain a directory name.
find . -type f -execdir prename 'tr/A-Z /a-z-/' {} +
What if you don't have the Perl rename
utility? The util-linux rename
utility won't help you here. You can put one more replacement in your loop.
for x in ./*; do
if [ -f "$x" ]; then
y=$(echo "$x" | tr 'A-Z-' 'a-z ')
mv "$x" "$y"
fi
done
With bash, you don't need to call tr
, you can use its string substitution constructs.
for x in ./*; do
if [ -f "$x" ]; then
lc=${x,,} # convert all letters to lowercase
y=${lc// /-} # replace spaces by hyphens
if [ "$x" != "$y" ]; then
mv "$x" "$y"
fi
fi
done
If you want to act on files in subdirectories as well, you can use a recursive glob (enabled by the globstar
option). Again, take care to leave the directory part alone if they may contain uppercase letters or spaces.
shopt -s globstar
for x in ./**/*; do
if [ -f "$x" ]; then
old_basename=${x##*/}
new_basename=${old_basename,,}
new_basename=${new_basename// /-}
if [ "$new_basename" != "$old_basename" ]; then
mv "${x%/*}/$old_basename" "${x%/*}/$new_basename"
fi
fi
done
In zsh, put autoload -U zmv
in your .zshrc
, and you can use the zmv
with the glob qualifier .
to match only regular files and parameter expansion constructs for the renaming:
zmv -Q '*(.)' '${(L)f// /-}'
To act on files in subdirectories as well:
zmv -Qw '**/*(.)' '$1${(L)2// /-}'
mv
and throw in anecho $lc
or two so you can see what you are doing, then work from there? See alsobash -x
: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_02_03.html Learning to fish is better than asking for fish ;) – goldilocks Mar 29 '13 at 13:46mv...
withecho $lc
did not change the output of the code. – Joe Mar 29 '13 at 13:59\
ls`` not'ls'
-- or better yet, justfor x in *
. My point about the echos and stuff was that you should consider the output of each command (eg.for x in 'ls'
is not at all what you think it is). – goldilocks Mar 29 '13 at 14:04Unexpected arguments passed on cmd line ./lower.sh: line 8: [: !=: unary operator expected
– Joe Mar 29 '13 at 14:06$x
is empty then. This is why the right hand (or both) side(s) in bash comparisons are often quoted ("$x"
expands to an empty string if there is nothing in $x, but just plain$x
expands to nothing if there is nothing in $x, so what bash sees isif [ $x != ]
. Also, in general use double[[
instead of[
with bash. – goldilocks Mar 29 '13 at 14:11if [ $lc = ]
". – goldilocks Mar 29 '13 at 14:20