I want to rename all files to lowercase in a directory without renaming subdirectories. I know rename function but it renames files and subdirectories which is not desired. Can you help?
5 Answers
Everything can be done in the perl expression.
Use -f
, the regular file test so that only regular files (not directories) will be renamed.
This works:
rename 'y/A-Z/a-z/ if -f;' ./*
(Using ./*
instead of *
ensures that no file names will be interpreted as options, even if the filename begins with a dash (-).

- 6,780
-
1Note that it renames regular files and symlinks to regular files. Use
-f && ! -l
if you want to exclude the symlinks. – Stéphane Chazelas Oct 26 '15 at 21:17 -
Also note that it only transliterates those ASCII English letters. It wouldn't rename STÉPHANE to stéphane for instance. Use
PERL_UNICODE=LSA rename '$_=lc if -f' -- *
for that. – Stéphane Chazelas Oct 26 '15 at 21:18 -
@stéphane-chazelas The
--
should be at the end of the options (since no options, immediately following the command name). This also means that the extra parenthesis around-f
are not necessary. – RobertL Oct 26 '15 at 21:40 -
True, but the original
rename
implementation as shipped withperl
up to 5.7.2 did not support options so a--
before the code would have been treated as code and caused an error (and after the code here would just have been ignored since it doesn't contain uppercase later), while for newer versions of rename that support options viaGetopt::Long
, that--
may appear after the code to mark the end of options (unless POSIXLY_CORRECT). Maybe a cleaner approach would berename 'y/A-Z/a-z/ if -f' ./*
– Stéphane Chazelas Oct 27 '15 at 07:39 -
I tried
rename -n "s/Season /s/ if -f;" ./*/*
and it sure looks like it's telling me it will rename the directories and not the files. – Andreas Aug 10 '20 at 00:12
With zsh
:
autoload zmv # best in ~/.zshrc
zmv '*(#q^/)' '${(L)f}'
To rename files of any type except directory. Or:
zmv '*(#q.)' '${(L)f}'
To rename regular files only. To also include hidden files, add the D
glob qualifier.
Example (using -n
for dry-run):
$ ls -alQF
total 132
drwxr-xr-x 3 stephane stephane 4096 Oct 27 09:11 "."/
drwxr-xr-x 533 stephane stephane 122880 Oct 27 09:11 ".."/
drwxr-xr-x 2 stephane stephane 4096 Oct 27 09:07 "DIR"/
lrwxrwxrwx 1 stephane stephane 3 Oct 27 09:07 "DIR-LINK" -> "DIR"/
prw-r--r-- 1 stephane stephane 0 Oct 27 09:07 "FIFO"|
-rw-r--r-- 1 stephane stephane 0 Oct 27 09:11 ".HIDDEN FILE"
-rw-r--r-- 1 stephane stephane 0 Oct 27 09:07 "HOLIDAYS IN МОСВА\nRED SQUARE.JPG"
lrwxrwxrwx 1 stephane stephane 23 Oct 27 09:08 "MY-RÉSUMÉ.PDF" -> "STÉPHANE'S RÉSUMÉ.PDF"
-rw-r--r-- 1 stephane stephane 0 Oct 27 09:07 "--READ-ME--.TXT"
srwxr-xr-x 1 stephane stephane 0 Oct 27 09:09 "SOCKET"=
-rw-r--r-- 1 stephane stephane 0 Oct 27 09:07 "STÉPHANE'S RÉSUMÉ.PDF"
$ zmv -n '*(#q^/)' '${(L)f}'
mv -- DIR-LINK dir-link
mv -- FIFO fifo
mv -- HOLIDAYS\ IN\ МОСВА$'\n'RED\ SQUARE.JPG holidays\ in\ мосва$'\n'red\ square.jpg
mv -- MY-RÉSUMÉ.PDF my-résumé.pdf
mv -- --READ-ME--.TXT --read-me--.txt
mv -- SOCKET socket
mv -- STÉPHANE\'S\ RÉSUMÉ.PDF stéphane\'s\ résumé.pdf
$ zmv -n '*(#q.)' '${(L)f}'
mv -- HOLIDAYS\ IN\ МОСВА$'\n'RED\ SQUARE.JPG holidays\ in\ мосва$'\n'red\ square.jpg
mv -- --READ-ME--.TXT --read-me--.txt
mv -- STÉPHANE\'S\ RÉSUMÉ.PDF stéphane\'s\ résumé.pdf
$ zmv -n '*(#qD.)' '${(L)f}'
mv -- .HIDDEN\ FILE .hidden\ file
mv -- HOLIDAYS\ IN\ МОСВА$'\n'RED\ SQUARE.JPG holidays\ in\ мосва$'\n'red\ square.jpg
mv -- --READ-ME--.TXT --read-me--.txt
mv -- STÉPHANE\'S\ RÉSUMÉ.PDF stéphane\'s\ résumé.pdf
See how some of them would break some symbolic links.

- 544,893
You can try
cd directory ; rename 'y/A-Z/a-z/' $(ls -p | grep -v /$) ; cd -
But better use find
cd directory ; find * -prune -type f -exec rename 'y/A-Z/a-z/' {} + ; cd -

- 14,916
-
1Your first example fails on files that have any special characters (e.g., space,
*
,?
and[
) in their names; see Why you shouldn't parse the output of ls(1). Also, it will fail altogether if you don'tcd
into the directory. The second one will work fairly well, I guess, although that's an unusual way to use-prune
. The more common way of usingfind
to look at only one directory isfind . -maxlevel 1
(although that might not be the most portable way). … (Cont’d) – G-Man Says 'Reinstate Monica' Oct 25 '15 at 23:13 -
1(Cont’d) … Also, both your answers ignore
.
files (e.g.,.PROFILE
); you can fix that by specifying-a
withls
or by usingfind . -maxlevel 1
. … … … … … … … … … … … … … … … … … … … … P.S. The question says, "I want to rename … files to lowercase … without renaming subdirectories." It doesn't say that the OP doesn't want to recurse into subdirectories. – G-Man Says 'Reinstate Monica' Oct 25 '15 at 23:13 -
The first one won't work as the output of
ls
won't include thedirectory/
prefix. – Stéphane Chazelas Oct 27 '15 at 10:21 -
-
if
directory
containsFOO
, then the output ofls -p directory
will beFOO
, notdirectory/FOO
, sorename '...' $(ls -p directory)
won't work becauseFOO
is not in the current directory, it's indirectory
. – Stéphane Chazelas Oct 27 '15 at 14:42 -
Converting to lowercase can be done by typesetting a var to lowercase.
EDIT: Replaced solution with find
for a solution with for
.
I first tried a solution using find
, hoping to find files only with -type f
.
This seems to work well:
I used -maxdepth 1
so I did not change anything in other folders (which would be slightly more difficult when a directory-name is in uppercase).
This resulted in
typeset -l smallfile
echo "Find solution looks OK but read more..."
find .-maxdepth 1 -type f| while read file; do
smallfile="${file}"
mv "${file}" "${smallfile}"
done
I thought this construction would handle filenames with spaces correctly, what I tested with filenames like "File with Spaces". @Chazelas commented correctly that the solution fails with filenames ending with a space ("Too bad "). Another comment explains how to fix the find
-command:find ... -print0 | while IFS= read -rd '' file
(your find
must support print0
, mostly bash
and zsh
).
Since I already am using a loop, I can just as well use test
to see of the current item (given by *
) is a regular file.
I found a solution with a simple for-loop and show it here including my testcode.
#!/bin/bash
clear
rm -rf testfiles 2>/dev/null
mkdir -p testfiles/SomeDir #2>/dev/null
mkdir testfiles/"Crazy Dir" 2>/dev/null
touch testfiles/Aha testfiles/"File with Spaces" testfiles/"End with space "
ls -lQ testfiles
echo ===================
typeset -l smallfile
for file in testfiles/* # Stack's formatting thinks /* starts comment, so I close that here: */
do
if [ -f "${file}" ]; then
smallfile="${file}"
# echo mv "${file}" "${smallfile}"
mv "${file}" "${smallfile}"
fi
done
echo ===================
ls -lQ testfiles
For the current dir, remove testdir/
from the for-loop.

- 736
-
That still has problems with file names that end in space or tab characters or contain backslash or newline characters.
for file in *
doesn't have problems with spaces as long as you remember to quote"$file"
. – Stéphane Chazelas Oct 27 '15 at 09:02 -
@Chazelas I changed the solution into a for-loop, but you earned my upvote with your zmv solution. – Walter A Oct 27 '15 at 12:53
-
For your
read
loop, tryfind ... -print0 | while IFS= read -rd '' file
(assumingzsh
orbash
). – Stéphane Chazelas Oct 27 '15 at 12:56
This oneliner will solve it:
find path-to-search -depth -type f -exec rename 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \;
Cya!

- 137
*
means everything. If you don't want to change everything, you need to replace*
with a list of candidates. – Bananguin Oct 25 '15 at 21:26rename
were you using? There are two incompatible versions floating around. – John1024 Oct 25 '15 at 21:30