3
.
├── subdirectory-A
│   ├── 1.jpg
│   ├── 1.tif
│   ├── 2.jpg
│   ├── 2.tif
│   ├── JPEG
│   └── TIF
└── subdirectory-B
    ├── 1.jpg
    ├── 1.tif
    ├── 2.jpg
    ├── 2.tif
    ├── JPEG
    └── TIF

Can anyone help please? I'm trying to find a way of moving all .tif files into the TIF directory and all .jpg files into the JPEG directory by running (mv) in a shell script from the local parent directory. I have been using

mv *.jpg JPEG/

within each sub directory but I need to do this job on a archive with over 17K directories and the manual option one dir at a time is not an option.

  • Do the TIF and JPEG already exist ? – cmak.fr Mar 19 at 22:00
  • Is there only 1 for path-depth? – cmak.fr Mar 19 at 22:22
2

You can do this with rename using bash’s globstar option:

shopt -s globstar
rename -n 's/.*\//$&JPEG\//' **/*.jpeg
rename -n 's/.*\//$&TIF\//' **/*.tif

The -n option lets it just output what it would do, remove it to actually perform the moving. What happens here is that rename goes over each e.g. .jpeg file in any subdirectory thanks to ** matching any number of subdirectories. It substitutes its path (everything until the last /) with itself ($&) followed by JPEG/, effectively moving the file to this directory.

If your list of arguments is too long the commands above will throw an error. Use this approach to work around the shell’s ARG_MAX limit:

printf '%s\0' **/*.jpeg | xargs -0 rename -n 's/.*\//$&JPEG\//'
printf '%s\0' **/*.tif | xargs -0 rename -n 's/.*\//$&TIF\//'

This uses the shell builtin printf to build a zero-delimited argument list which is piped to xargs which calls rename with the maximum number of arguments.

Example run

$ tree
.
├── subdirectory-A
│   ├── 1.jpeg
│   ├── 1.tif
│   ├── 2.jpeg
│   ├── 2.tif
│   ├── JPEG
│   └── TIF
└── subdirectory-B
    ├── 1.jpeg
    ├── 1.tif
    ├── 2.jpeg
    ├── 2.tif
    ├── JPEG
    └── TIF
$ shopt -s globstar
$ rename -n 's/.*\//$&JPEG\//' **/*.jpeg
rename(subdirectory-A/1.jpeg, subdirectory-A/JPEG/1.jpeg)
rename(subdirectory-A/2.jpeg, subdirectory-A/JPEG/2.jpeg)
rename(subdirectory-B/1.jpeg, subdirectory-B/JPEG/1.jpeg)
rename(subdirectory-B/2.jpeg, subdirectory-B/JPEG/2.jpeg)
$ rename 's/.*\//$&JPEG\//' **/*.jpeg
$ printf '%s\0' **/*.tif | xargs -0 rename 's/.*\//$&TIF\//'
$ tree
.
├── subdirectory-A
│   ├── JPEG
│   │   ├── 1.jpeg
│   │   └── 2.jpeg
│   └── TIF
│       ├── 1.tif
│       └── 2.tif
└── subdirectory-B
    ├── JPEG
    │   ├── 1.jpeg
    │   └── 2.jpeg
    └── TIF
        ├── 1.tif
        └── 2.tif
  • Yes that did it! Thanks for your help - I still have quite a lot to learn with Linux. :) – Ols Mar 20 at 0:26
2

The first thing that comes to my mind is the following Bash loop:

#!/bin/bash
for dir in */     # or use: subdirectory*/
do
    cd "$dir"
    mv *jpg JPG/
    mv *tif TIF/
    cd ..
done

Example of usage as inline command:

$ mkdir -p subdirectory-{A,B}/{TIF,JPG}; touch subdirectory-{A,B}/{1,2}.{jpg,tif}

$ for dir in */; do cd "$dir"; mv *jpg JPG/; mv *tif TIF/; cd ..; done

$ tree
.
├── subdirectory-A
│   ├── JPG
│   │   ├── 1.jpg
│   │   └── 2.jpg
│   └── TIF
│       ├── 1.tif
│       └── 2.tif
└── subdirectory-B
    ├── JPG
    │   ├── 1.jpg
    │   └── 2.jpg
    └── TIF
        ├── 1.tif
        └── 2.tif

6 directories, 8 files

Related questions:

2

Since you mentioned "over 17K directories", find and xargs spring to mind.

# do this part Only Once  
cat >./TheScript <<"EOF"
#!/bin/bash
while $# -gt 0 ; do
    cd "$1"
    find . -maxdepth 1 -type f -name '*.jpg' -print | xargs --no-run-if-empty mv --target-directory=JPG
    find . -maxdepth 1 -type f -name '*.tif' -print | xargs --no-run-if-empty mv --target-directory=TIF
    cd "$OLDPWD"
    shift
done
exit 0
EOF
chmod +x ./TheScript
# end of "Only Once"


find . -type d \! -name 'JPG' -a \! -name 'TIF` -print |\
    xargs $PWD/.TheScript

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.