Recursively rename files and directories
Solution 1
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
Solution 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 {} ";"
Solution 3
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
Solution 4
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
Related videos on Youtube
brad of the near north
Updated on September 18, 2022Comments
-
brad of the near north almost 2 years
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.-
enzotib almost 13 yearsBe 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 almost 13 yearsDon't this glob match only directory, but the user wants only files?
-
jasonwryan almost 13 years@enzotib Indeed, it did. I have updated it. Thanks.
-
ShadowFlame almost 13 yearsFWIW, this syntax was taken from zsh, which, IMHO, is a far better shell.
-
Peter.O almost 13 yearsThanks Gilles... About 'echo': interesting, and noteworthy:
x="-n"; echo "$x"
treats the -n as an opiton and prints nothing, butx="-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 almost 13 yearsglobstar 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... -
jasonwryan almost 13 yearsOuch: that is not pretty...
-
AbstProcDo over 5 yearswhy is it
**/*
rather than*/**
?