You can extract the numbers by parameter expansion. ${f:5:2}
selects the two characters from the fifth position of the variable $f
.
#! /bin/bash
for f in aula-??.?.mp4 ; do
num=${f:5:2}
mv "$f" Aula"$num"/
done
To extract two digits from the filename if the position is not fixed, use
#! /bin/bash
for f in *.mp4 ; do
if [[ $f =~ ([0-9][0-9]) ]] ; then
num=${BASH_REMATCH[1]}
mv "$f" Aula"$num"/
else
echo "Can't extract number form '$f'" >&2
fi
done
As already noted, the short answer is "yes".
The long answer is: You can do it with a bash script that uses awk
to extract the filename elements you want to base your directory structure on. It could look something like this (where more emphasis is placed on readability than "one-liner" compactness).
#!/bin/bash
for FILE in p-*
do
if [[ ! -f $FILE ]]; then continue; fi
LVL1="$(awk '{match($1,"^p-([[:digit:]]+)_[[:print:]]*",fields); print fields[1]}' <<< $FILE)"
LVL2="$(awk '{match($1,"^p-([[:digit:]]+)_n-([[:digit:]]+)_[[:print:]]*",fields); print fields[2]}' <<< $FILE)"
echo "move $FILE to p-$LVL1/n-$LVL2"
if [[ ! -d "p-$LVL1" ]]
then
mkdir "p-$LVL1"
fi
if [[ ! -d "p-$LVL1/n-$LVL2" ]]
then
mkdir "p-$LVL1/n-$LVL2"
fi
mv $FILE "p-$LVL1/n-$LVL2"
done
To explain:
- We perform a loop over all files starting with "p-" in the current directory.
- The first instruction in the loop ensures that the file exists and is a workaround for empty directories (the reason why this is necessary is that on this forum, you will always be told not to parse the output of
ls
, so something like FILES=$(ls p-*); for FILE in $FILES; do ...
would be considered a no-go).
- Then, we extract the numerals between
p-
and _n
needed to generate the first level of your directory structure using awk
(as you suspected, with regular expressions), the same for the numerals between n-
and _a
for the second level. The idea is to use the match
function which not only looks for the place where the specified regular expression occurs in your input, but also gives you the "completed" value of all elements enclosed in round brackets ( ... )
in the array "fields".
- Third, we check if the directories for the first and second level of your intended directory structure already exist. If not, we create them.
- Last, we move the file to the target directory.
For more information, have a look at the Advanced bash scripting guide and the GNU Awk Users Guide.
Once you are more firm in scripting and regular expressions, you can make this much more compact; in the above script, for example, the generation of the directory/subdirectory path could easily be contracted to just one awk
call.
For one, since the directory names are actually p-<number>
and n-<number>
, the same as in your filename, we could have let awk
do the work to extract these characters for us, too, by writing
match($1,"(^p-[[:digit:]]+)_(n-[[:digit:]]+)_[[:print:]]*",fields)
We can further offload work to awk
by having it generate the directory-subdirectory path at the same time with a suitable argument of print
:
awk '{match($1,"(^p-[[:digit:]]+)_(n-[[:digit:]]+)_[[:print:]]*",fields); print fields[1] "/" fields[2]}'
would readily yield (e.g.) p-12345/n-384
for file p-12345_n-384_a-583.pdf
. If we combine that with the usage of mkdir -p
as indicated by @wurtel, the script could look like
for FILE in p-*
do
if [[ ! -f $FILE ]]; then continue; fi
TARGET="$(awk '{match($1,"(^p-[[:digit:]]+)_(n-[[:digit:]]+)_[[:print:]]*",fields); print fields[1] "/" fields[2]}' <<< $FILE)"
echo "move $FILE to $TARGET"
mkdir -p "$TARGET"
mv $FILE $TARGET
done
Best Answer
Here's a bit of a one liner that will do what you want:
Here's the same command in expanded form so you can see what's going on:
Details
The above first assumes that the output directory of just the letters doesn't exist and so will create it,
The
for
loop works as follows, looping through all the files intstdir/*
. It then determines thebasename
of this path, and stores it in the variable,$FILE
. Each iteration through the loop is stored in the variable$i
.We then use Bashes ability to return the 1st character of the named variable,
$FILE
, and then usetr
to convert any lowercase characters to uppers.Breaking this down a bit more:
With the
tr
code you can now see what's going on:The rest of the command just moves the files into their corresponding first letter directory.
Example
Say we have this directory of files:
After running the one liner: