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
    Commented 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
    Commented 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
    Commented 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
    Commented 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
    Commented 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
    Commented 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}"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .