Consider I've directories named according to years and containing pdf files according to subject-code.

ls output is:

$ ls -l
total 32
drwxrwxrwx 1 root root 4096 Apr  8 08:52 May-June-2011
drwxrwxrwx 1 root root 4096 Apr  8 08:52 Nov-Dec-2011
drwxrwxrwx 1 root root 4096 Apr  8 20:36 Summer-2012
drwxrwxrwx 1 root root 4096 Apr  8 08:52 Summer-2013
drwxrwxrwx 1 root root 4096 Apr  8 08:52 Summer-2014
drwxrwxrwx 1 root root 4096 Apr  8 08:52 Winter-2012
drwxrwxrwx 1 root root 4096 Apr  8 08:52 Winter-2013
drwxrwxrwx 1 root root 4096 Apr  8 08:52 Winter-2014

Each directories contains pdf files according to subject-code:-

ls -l May-June-2011/
total 808
-rwxrwxrwx 1 root root 104193 May  1  2011 161901.pdf
-rwxrwxrwx 1 root root 103380 May  1  2011 161902.pdf
-rwxrwxrwx 1 root root 115664 May  1  2011 161903.pdf
-rwxrwxrwx 1 root root  88953 May  1  2011 161904.pdf
-rwxrwxrwx 1 root root 179268 May  1  2011 161905.pdf
-rwxrwxrwx 1 root root 116158 May 24  2011 161906.pdf
-rwxrwxrwx 1 root root 106033 May  1  2011 161907.pdf

In other words each directory has 16190{1..7}.pdf

Now suppose I want to move all (from all directory mentioned) 161901.pdf to a specific directory (say xyz) with renaming to it's parent folder's name.pdf.

Explanation :-

Here is list of all 161901.pdf:

$ find -name 161901.pdf

I want that ./May-June-2011/161901.pdf should be moved into xyz with new name May-June-2011.pdf (name of directory inside which file is). i.e move ./May-June-2011/161901.pdf to ./xyz/May-June-2011.pdf

Similarly ./Nov-Dec-2011/161901.pdf to ./xyz/Nov-Dec-2011.pdf, ./Summer-2012/161901.pdf to ./xyz/Summer-2012.pdf and so on (up to ./Winter-2014/161901.pdf to ./xyz/Winter-2014.pdf).

The expected output for ls xyz is:

$ls xyz

How can I accomplish this? (how to find -exec or with loop or something else)

  • 1
    great, looking for the same!
    – imbr
    Dec 2, 2016 at 18:20

7 Answers 7


Perl module Unicode::Tussle comes with a very useful script named rename (which is unfortunate, because the name clashes with the standard rename(1) on Linux). With it, you could do something like this:

mkdir xyz
find . -name '*.pdf' -print0 | \
    rename -0 's!^\.!xyz!;  s!/[^/]*\.pdf$!.pdf!'

Without Perl, you could still do the same thing with a bit of shell plumbing:

mkdir xyz
find . -name '*.pdf' | sed 's/^/mv /' >src
find . -name '*.pdf' | sed 's!^\./!xyz/!; s!/[^/]*\.pdf$!.pdf!' >dest
join src dest | sh -
rm -f src dest

And, as I'm sure other people will be quick to point out, you can even put it on a single line, using <(...) process substitution. I'm not sure that would make things more legible though. Computers these days are powerful enough to allow a more verbose syntax. ;)

  • +1: cool - I didn new about Uncode::Tussle kit. (In perl.deb package you get prename a not so unicoded variant of rename)
    – JJoao
    Apr 29, 2015 at 9:49
  • 1
    Aside from being Unicode-aware, the great thing about Uncode::Tussle's rename is that it can use full Perl substitutions. It's a huge time saver in many situations.
    – lcd047
    Apr 29, 2015 at 10:02
  • I agree!!. prename is the same thing. (it is in face a 20 line very readable perl script)
    – JJoao
    Apr 29, 2015 at 10:12

find is rarely useful when you don't need to traverse a directory tree recursively. Here a simple loop and shell wildcards are enough.

for x in */161901.pdf; do
  mv -- "$x" "xyz/${x%/*}.pdf"

Or, with the Perl-based rename command on Debian, Ubuntu and derivatives (prename on Arch, not the Linux rename command on other distributiosn):

rename 's!/.*!!; $_="xyz/$_.pdf"' */161901.pdf

Or, with zsh:

autoload -U zmv
zmv '(*)/161901.pdf' 'xyz/$1.pdf'

One way is to use find -print and pipe the filelist to awk where you can transform it to whatever you want, e.g. a cp command:

$ find -name 161901.pdf -print | awk -v TARGET=xyz -F'/' '{ printf "cp %s %s/%s.pdf\n", $0, TARGET, $2; }'
cp ./May-June-2011/161901.pdf xyz/May-June-2011.pdf
cp ./Nov-Dec-2011/161901.pdf xyz/Nov-Dec-2011.pdf

and this can then be piped to a shell to get executed:

find -name 161901.pdf -print | awk -v TARGET=xyz -F'/' '{ printf "cp %s %s/%s.pdf\n", $0, TARGET, $2; }' | sh 
  • I appreciate your use of awk!
    – Pandya
    Apr 29, 2015 at 13:04

Sweet and simple:


find . -name 161901.pdf | while read -r line
  # this will create new filenames e.g. ./May-2013/161901.pdf will be now May-2013-161901.pdf
  filename=$(echo $line | grep -oP "\/\K.+$" | sed 's/\//-/g')
  # now just move the files
  mv $line xyz/$filename

So, essentially it's just a 2-step process. Create new filenames and move 'em.

  • Thank-you for reminding while read -r line!
    – Pandya
    Apr 29, 2015 at 13:08

If you have rename / prename command

find -name 161901.pdf -exec prename -n 's!/!-!g; s!\W*!xyz/!' {} +

and remove -n if you like what you see.

(Larry Wall and Robin Barker' prename is installed with perl.deb package; Brian d Foy Unicode::Tussle rename also works)


Later I found while read -r line very helpful and I've succeed with following command:-

find -name 161901.pdf | while read -r line; do mv $line ./xyz/$(echo $line | cut -d "/" -f 2).pdf; done


  • Here find -name 161901.pdf list the founded files (already mentioned in question) which piped to while loop in while path stored in variable line.
  • cut -d "/" -f 2 filters the directory name e.g-May-June-2011
  • And finally mv $line ./xyz/$(echo $line | cut -d "/" -f 2).pdf moves files (with renamed to directory name in which they were)

So, find+whileloop with mv using cut for naming accomplishes what I want.


This could be a possible solution. The argument of the script is the directory in which you are searching, and now it is just printing what will do:


find "$1" -type f -name '161901.pdf' -print0 | while IFS= read -r -d '' f
    # get folder name
    d="$(basename "${f%/*}")"
    echo cp $f "xyz/${target}"

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.