Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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.

share|improve this question
2  
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

5 Answers 5

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
share|improve this answer
    
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 3:28

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 {} ";"
share|improve this answer

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

share|improve this answer
    
Don't this glob match only directory, but the user wants only files? –  enzotib Aug 10 '11 at 6:46
    
@enzotib Indeed, it did. I have updated it. Thanks. –  jasonwryan Aug 10 '11 at 6: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 .. 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 9:57
    
Ouch: that is not pretty... –  jasonwryan Aug 11 '11 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
share|improve this answer

How about this?

#!/bin/bash
IFS=!
ARRAY=(`find . -type f`)
for i in ${ARRAY[*]}; do
    echo "$i"
    mv "$fname" $(echo "$fname" | sha1sum | cut -f1 -d' ')
done

I'm using find instead of ls.

share|improve this answer
1  
ARRAY=($(command)) do not work correctly if you have filenames containing spaces. –  enzotib Aug 10 '11 at 6:45

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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