1

I need to move files by reading their names. I have to move system generated files and the names include a time stamp. I need to move the files by reading the file name by month.

If the file name contains 201601*.txt, then move to 2016/January directory. If the file name contains 201602*.txt then move to 2016/February directory etc.

  • That doesn't look good in the 2016-directory. Wouldn't you rather use subdirectories 01..12 instead? And should the name be shortened by that? – ott-- Jun 20 '16 at 10:07
2

Well, if the date strings are in the file names and all the files are in the same directory, you could do:

mv 201601*.txt 2016/January

Doing this 12 times manually would be a pain, so I would create a list with the number and corresponding month name:

$ paste <(printf '%s\n' {01..12}) <(cal 2016 | grep -Po '\s+\K[A-Z]\w{2,}') 
01  January
02  February
03  March
04  April
05  May
06  June
07  July
08  August
09  September
10  October
11  November
12  December

And then parse it to build the mv commands:

$ paste <(printf '%s\n' {01..12}) <(cal 2016 | grep -Po '\s+\K[A-Z]\w{2,}') | 
    while read num month; do echo mv "*2016$num*" 2016/"$month"; done
mv *201601* 2016/January
mv *201602* 2016/February
mv *201603* 2016/March
mv *201604* 2016/April
mv *201605* 2016/May
mv *201606* 2016/June
mv *201607* 2016/July
mv *201608* 2016/August
mv *201609* 2016/September
mv *201610* 2016/October
mv *201611* 2016/November
mv *201612* 2016/December

Once you're sure that does what you need, remove the echo to actually move the files:

paste <(printf '%s\n' {01..12}) <(cal 2016 | grep -Po '\s+\K[A-Z]\w{2,}') | 
    while read num month; do echo mv "*2016$num*" 2016/"$month"; done

Explanation

The grep -Po '\s+\K[A-Z]\w{2,}') will run grep with Perl Compatible Regular Expressions (-P) and will print only the matching portion(s) of each line (-o). The regular expression used will look for one or more whitespace characters (\s+) followed by a capital letter ([A-Z]) and 2 or more word characters (\w{2,}). The \K means "ignore everything before this" which will cause the command to only print the part of the matching strings after the whitespace. The whole thing simply prints the list of months.

  • HI,Pls explain the work of " '\s+\K[A-Z]\w{2,}" part – user1924765 Jun 20 '16 at 11:29
  • @user1924765 is that clearer? – terdon Jun 20 '16 at 11:32
  • Also, @user1924765, if this answer solved your issue, please take a moment and accept it by clicking on the check mark to the left. That will mark the question as answered and is the way thanks are expressed on the Stack Exchange sites. – terdon Jun 20 '16 at 11:52
1

Zsh has a nice function to rename files based on name patterns: zmv. In the replacement pattern, $f designates the whole original name and $1, $2 etc. are the parenthesized groups. Use an array to store month names.

autoload -U zmv
months=(January February March April May June July August September October November December)
zmv '(2016)(<1-12>)*.txt' '$1/$months[$2]/$f'

Because the pattern here is simple, it isn't very painful to adapt this to bash and ksh, with a loop. You need to take care of a few things: strip the leading 0 from the month number so that it isn't interpreted as octal; array indices start at 0.

months=(January February March April May June July August September October November December)
for f in 2016[0-9][0-9]*; do
  y=${f:0:4} m=${f:4:2}
  mv -- "$f" "$y/${months[${m#0}]}/$f"
done

Another approach is the Perl rename script (either the Debian version or the Unicode::Tussle version).

rename 'BEGIN {@months = qw(January February March April May June July August September October November December)}
        s!^([0-9]{4})([0-9]{2})!$1/$months[$2]/&!' 2016[0-9][0-9]*
0

Here is a bash solution that runs through the files, moving those that match the yyyymm component (for values of yyyy in the range 2000-2099):

months=('' January February March April May June July August September October November December)

for f in 20[0-9][0-9][0-3][0-9]*
do
    year=$(echo "$f" | grep -Po '^20\d\d')           # Extract the four digit year, 20nn
    mm=$(echo "$f" | grep -Po '^(?<=20\d\d)\d\d')    # Find the two digit month number 01-12
    month=${months[$mm]}                             # Convert to a month name

    test -n "$year" -a -n "$month" && echo mv "$f" "$yyyy/$month/$f"
done

Remove the echo prefix from the mv when you are ready to run.


If you know that all the files begin with 2016 you can simplify the processing somewhat:

for f in 2016[0-3][0-9]*
do
    mm=$(echo "$f" | grep -Po '^(?<=2016)\d\d')    # Find the two digit month number 01-12
    month=${months[$mm]}                           # Convert to a month name

    test -n "$month" && echo mv "$f" "2016/$month/$f"
done

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.