8

I'm using this command to rename files:

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

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.

muru
  • 69,900
  • 13
  • 192
  • 292
  • 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 '11 at 14:54

4 Answers4

8

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
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • 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 '11 at 03:28
2

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

#!/bin/bash
shopt -s globstar
for fname in **/*; do 
  if [ -f "$fname" ]; then
    mv ...
  fi
done

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.

http://wiki.bash-hackers.org/bash4

jasonwryan
  • 71,734
  • 34
  • 193
  • 226
  • Don't this glob match only directory, but the user wants only files? – enzotib Aug 10 '11 at 06:46
  • @enzotib Indeed, it did. I have updated it. Thanks. – jasonwryan Aug 10 '11 at 06:58
  • FWIW, this syntax was taken from zsh, which, IMHO, is a far better shell. – wfaulk Aug 10 '11 at 13:32
  • [globstar in bash 4 follows directory symlinks](http://en.chys.info/2009/04/globstar-in-bash-4-follows-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 '11 at 09:57
  • Ouch: that is not pretty... – jasonwryan Aug 11 '11 at 10:35
  • why is it `**/*` rather than `*/**`? – AbstProcDo Oct 28 '18 at 05:27
2

Create a helper script /tmp/tmp.sh:

#!/bin/bash
mv "$1" $(echo "$1" | sha1sum | cut -f1 -d' ')

make it executable, then invoke it:

find . -type f -execdir /tmp/tmp.sh {} ";"
user unknown
  • 10,267
  • 3
  • 35
  • 58
1

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
Peter.O
  • 32,426
  • 28
  • 115
  • 163