Swissdude
Swissdude

Reputation: 3556

bash moving files to subdirectories according to their name

I have files that are named like this:

A-1230.pdf
A-2450.pdf
A-6780.pdf

B-1230.pdf
B-2450.pdf
B-6780.pdf

... and a directory-structure like this:

A
- 000
- 001
-...
- 999

B
- 000
- 001
-...
- 999

No I want to move the files to their corresponding subdirectories. Means

A-1230.pdf goes into A -> 123
B-2450.pdf goes into B -> 245

And so on.

I tried the following with bash:

mv +([A-Z]-[0-9][0-9][0-9]*.pdf) $1/$2$3$4

But that's not working. How do I use backreferences and capturing groups in bash correctly?

Thanks for any help!

Upvotes: 0

Views: 87

Answers (2)

kvantour
kvantour

Reputation: 26471

What you are attempting to achieve is what is called back-referencing. Unfortunately, there is no such concept for globular expressions. Expressions such as:

mv (*).pdf $1/
cp ([0-9])-(*).txt $2-$1.text

are simply not possible in any shell (bash, zsh, ksh, csh).

Workarounds exist by looping over every file and post process it a bit:

for file in [A-Z]-[0-9][0-9][0-9]*.pdf; do
   dir="${file:0:5}"; dir="${dir/-/\/}"
   mkdir -p "$dir"
   mv "$file" "$dir"
done

or a bit more intelligently as is done by Charles Duffy

ZSH developed, for tasks like this, a set of separate tools (zcp, zmv and zln) which allow back referencing. So your task could be performed as:

zmv -n '([A-Z])-([0-9][0-9][0-9])[0-9].pdf' '$1/$2/'

The -n flag shows what it will do but not actually perform the action as it acts as a dry-run. remove the flag to perform the actual moves.

note: ZSH does not have back-referencing for globular expressions. Notice the single quotes used in the zmv example above. They prohibit globbing. The FROM and DEST part are arguments past to those tools. The tools are also not found in any $PATH as they are part of the zsh system. This in contrast to cp, mv and ln which are actual binaries.

Upvotes: 0

Charles Duffy
Charles Duffy

Reputation: 295288

Regex support in bash is used with [[ $string =~ $regex ]], and places capture groups in the array BASH_REMATCH.

for file in ?-*.pdf; do
  [[ $file =~ ([[:alpha:]])-([[:digit:]]{3}).* ]] || continue
  mkdir -p -- "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}" || continue 
  mv -- "$file" "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}/"
done

Upvotes: 3

Related Questions