I was looking for a script that changes the filename and directory name to lowercase and replaces any white spaces with "-". I managed to find the script below inside another script but I do not fully understand how it works. I would really appreciate it if someone could walk me through it.

find ./* -depth -type d -exec sh -c '
t=${0%/*}/$(printf %s "${0##*/}" | tr "[:upper:]" "[:lower:]");
[ "$t" = "$0" ] || mv -i "$0" "$t"
' {} \;
  • 1
    Which bits do you understand? – bu5hman Feb 1 '18 at 17:05
  • I understand "find ./* -depth -type d" and "tr "[:upper:]" "[:lower:]" – Alex Feb 1 '18 at 17:49
find ./* -depth -type d -exec sh -c ' ... ' {} \;

finds all directories (-type d) in the tree and runs that shell snippet for each of them, with $0 set to the current name. -depth makes it process the contents of each directory first, and the directory itself last. (otherwise the renaming messes things up.)

t=${0%/*}/$(printf %s "${0##*/}" | tr "[:upper:]" "[:lower:]");

${0%/*} removes the last slash and everything after it from $0, i.e. leaves the directory name. ${0##*/} is basically the opposite. (They're types of parameter expansion) The printf | tr is used to change the final part of the name to lower case. The result is assigned to t. ($(...) is command substitution)

[ "$t" = "$0" ] || mv -i "$0" "$t"

Tests to see if the new name in t and the old name in $0 are the same, and if not, then calls mv on them.

The silly parts here are that find could be called just as find . -depth ..., there's no need for the filename wildcard (find ./*) since finding the file names is what find does. If they used -execdir instead of -exec, there would be no need to deal with the directory part of the file names. And using $0 for the target file name is a bit bad form, it's meant for the name of the script itself.

You mentioned renaming files and directories, and changing whitespace to dashes. As far as I can see, the command here does neither. find -type d only catches directories, you'd need to use -type f to catch only regular files, or leave it out to ignore the type. Also, there's nothing about whitespace in the tr, but you could change it to, say tr "[:upper:][:space:]" "[:lower:][-*]" to also change all whitespace to dashes.

  • @steeldriver, ah, true, I'd already forgotten that when I reached the end... – ilkkachu Feb 1 '18 at 18:34

I would use larry walls rename, it is available on many distros. Including Debian, and probably derivatives.

rename -E 's/-//g' -E 's/(.*)/\L$1/g' *

First expression search and replaces - with nothing. second expression search and replaces everything (.*) with the same thing $1 but converted to lowercase \L

  • The main problem with rename is it's inconsistent. The default rename is different between debian and red hat variants, and even Debian has multiple providers for /usr/bin/rename. – jordanm Feb 1 '18 at 17:34
  • ... and is not recursive ... plus I was looking for an explanation not a different method. Thank you very much for your input! – Alex Feb 1 '18 at 17:48

The script you present does some things to extract the directory and changes it to lowercase. There is no change of white space to -.

To change space to - and lowercase the string, sed works fine:

sed -e 's/[[:space:]]/-/g' -e 's/\(.*\)/\L\1/'

So, this script should work:

find ./* -depth -type d -execdir sh -c '
t=$(printf %s "${1}" | sed -e 's/[[:space:]]/-/g' -e "s/\(.*\)/\L\1/")
[ "$t" = "$1" ] || echo mv -i "$1" "$t"
' sh {} \;

Using -depth and -execdir avoids the need to extract the dir name.
The type d limits the selected elements to only directories.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.