-1

I am trying to learn how a couple of commands works and I want to make a simple script that will change uppercase letters in file names to lowercase. I created that script:

find * -exec bash -c \
     'echo "${1##*/}" | tr "[:upper:]" "[:lower:]";' _ {} \;

It works, but it only writes with echo lowercased file names, instead of changing them. On the other hand this piece of code:

 find * -exec bash -c \
     'tr "[:upper:]" "[:lower:]";' _ {} \;

acts as if I would only use:

tr "[:upper:]" "[:lower:]"

So, I want to learn and understand how to pass filenames that I can get from find and do anything with them using tr. Went already to manuals and google and found nothing.

Cortex0101
  • 831
  • 2
  • 12
  • 28
Jkt44
  • 47
  • 1
  • 7

2 Answers2

0

Well ... tr just does that; translates one character set to another, here upper to lower. To actually change a filename you need to use mv.

Capture the names that actually would change, and then use mv on those; you can't just go mv for every file, because that would give you error messages of the type mv: 'blah' and 'blah' are the same file ...

tink
  • 14,342
  • 4
  • 46
  • 50
0

As @tink points out, if you want to rename the files, you need to actually invoke mv. tr just translates strings (and frankly, there's no good reason to use it for converting shell variables between upper and lower case if you're using bash 4.0 or later, which has native case conversion available via parameter expansion syntax).

The Complex Actions section of UsingFind shows a way to do this quite similar to the code you were already running:

find . -depth -exec bash -c '
  for old_name; do
    new_name=$(tr "[:upper:]" "[:lower:]" <<<"$old_name")
    [[ $old_name = "$new_name" ]] && continue
    mv -- "$old_name" "$new_name"
  done
' _ {} +;

...though personally, I would tend to avoid much of the complexity by performing the mv operations in the parent shell:

while IFS= read -r -d '' old_name; do
  new_name=$(tr "[:upper:]" "[:lower:]" <<<"$old_name") # or new_name=${old_name,,}
  [[ $old_name = "$new_name" ]] && continue
  mv -- "$old_name" "$new_name"
done < <(find . -type f -print0)

Note that in the first case, we're using find . -exec {} +. Using find . is more reliable than find * since it doesn't rely on shell globbing; and -exec ... _ {} + is more efficient than -exec ... _ {} \; since it passes multiple names to one command, as opposed to starting a separate copy of bash for each name found.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441