I'm using this command to rename files:

for fname in *;
    mv "$fname" $(echo "$fname" | sha1sum | cut -f1 -d' ')

But it only renames in the current directory. Let's say I have many directories, and each directory contains some other directories, and last directory tree contains files. I want to rename them with random characters.

I think find . -type f should work, and have tried it, but still did not get any working command.

  • 3
    Be aware that echo will insert a newline at end of of output, so modifying the sha1sum output with respect to what you would expect for the bare string.
    – enzotib
    Aug 10, 2011 at 14:54

4 Answers 4


With find:

find . -type f -exec sh -c 'SHELL COMMAND' {} \;

This invokes SHELL COMMAND on each found file in turn; the file name is "$0". Thus:

find . -type f -exec sh -c '
    mv "$0" "${0%/*}/$(printf "%s\n" "${0##*/}" | sha1sum | cut -d" " -f1)"
' {} \;

(Note the use of printf rather than echo, in case you have a file called -e or -n or a few other problematic cases that echo mangles.)

You can make this a little faster by invoking the shell in batches.

find . -type f -exec sh -c 'for x; do
      mv "$x" "${x%/*}/$(printf "%s\n" "${x##*/}" | sha1sum | cut -d" " -f1)";
    done' _ {} +

In zsh, there's an easy way to match all the files in the current directory and its subdirectories recursively. The . glob qualifier restricts the matches to regular files, and D includes dot files.

for x in **/*(.D); do mv …; done

In bash ≥4, you can run shopt -s globstar and use **/* to match all files in the current directory and its subdirectories recursively. You'll need to filter regular files in the loop.

shopt -s globstar; GLOBIGNORE=".:.."
for x in **/*; do if [[ -f $x ]]; then mv …; done
  • Thanks Gilles... About 'echo': interesting, and noteworthy: x="-n"; echo "$x" treats the -n as an opiton and prints nothing, but x="-n "; echo "$x " prints -n with the trailing space.. Thinking about it, it makes sense, as "-n" resolves to a bare -n option, and "-n " doesn't... but echo doesn't have the special "--" option to protect from this..
    – Peter.O
    Aug 11, 2011 at 3:28

Create a helper script /tmp/tmp.sh:

mv "$1" $(echo "$1" | sha1sum | cut -f1 -d' ')

make it executable, then invoke it:

find . -type f -execdir /tmp/tmp.sh {} ";"

If you are using Bash 4+, you can do:

shopt -s globstar
for fname in **/*; do 
  if [ -f "$fname" ]; then
    mv ...

From the Bash Hacker's Wiki:

There's a new shell option globstar. When enabled, Bash will perform recursive globbing on ** – this means it matches all directories and files from the current position in the filesystem, rather that only the current level.


  • Don't this glob match only directory, but the user wants only files?
    – enzotib
    Aug 10, 2011 at 6:46
  • @enzotib Indeed, it did. I have updated it. Thanks.
    – jasonwryan
    Aug 10, 2011 at 6:58
  • FWIW, this syntax was taken from zsh, which, IMHO, is a far better shell.
    – wfaulk
    Aug 10, 2011 at 13:32
  • globstar in bash 4 follows directory symlinks .. I'm getting filenames like this: ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/ztest/b/z.... ... It doesn't crash, oddly enough... maybe it is somehow limited in the number of loop-de-loops it does...
    – Peter.O
    Aug 11, 2011 at 9:57
  • Ouch: that is not pretty...
    – jasonwryan
    Aug 11, 2011 at 10:35

This handles all whitespace ok...

set -f; IFS= 
while read -r -d $'\0' fname ;do
    mv ...
done < <(find . -type f -name '*' -print0)
set +f; IFS=$' \t\n' # you don't have to reset unless it effects subsequent code

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.