4

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?

mony
  • 61
  • Welcome to UL.sx! Please, always provide as much information about your problem as possible and explain your previous attempts; maybe even give an example. As it is, your question cannot be answered. – Bananguin Oct 25 '15 at 21:21
  • I used rename 'y/A-Z/a-z/' * and it renamed all files and subdirectories in a directory. All I want is to rename files only – mony Oct 25 '15 at 21:25
  • 1
    please edit your question to include all information. * 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:26
  • Which rename were you using? There are two incompatible versions floating around. – John1024 Oct 25 '15 at 21:30
  • Version if I am correct: perl v5.18.2. Bananguin: I found that instead of * I can use eg. *.txt for txt extension. But I don't know how to apply it to all files excluding directories – mony Oct 25 '15 at 21:40

5 Answers5

4

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 (-).

RobertL
  • 6,780
  • 1
    Note 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 with perl 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 via Getopt::Long, that -- may appear after the code to mark the end of options (unless POSIXLY_CORRECT). Maybe a cleaner approach would be rename '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
2

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.

1

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 -
Costas
  • 14,916
  • 1
    Your 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't cd 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 using find to look at only one directory is find . -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 with ls or by using find . -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 the directory/ prefix. – Stéphane Chazelas Oct 27 '15 at 10:21
  • @StéphaneChazelas Script is work as expected. – Costas Oct 27 '15 at 14:34
  • if directory contains FOO, then the output of ls -p directory will be FOO, not directory/FOO, so rename '...' $(ls -p directory) won't work because FOO is not in the current directory, it's in directory. – Stéphane Chazelas Oct 27 '15 at 14:42
  • @StéphaneChazelas Ah! I understand at last. Thank you – Costas Oct 27 '15 at 19:04
1

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.

Walter A
  • 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, try find ... -print0 | while IFS= read -rd '' file (assuming zsh or bash). – Stéphane Chazelas Oct 27 '15 at 12:56
0

This oneliner will solve it:

find path-to-search -depth -type f -exec rename 's/(.*)\/([^\/]*)/$1\/\L$2/' {} \;

Cya!