1

I have about a thousand PDF files with names like Foo_Jan_2013.pdf, Bar_Feb_2012.pdf, Foo_Mar_2013.pdf, Bar_Mar_2013.pdf. I want to rename these files replacing the month names with numbers, such as Foo_01_2013.pdf, Bar_02_2012.pdf, Foo_03_2013.pdf, Bar_03_2013.pdf.

One approach I tried is to define two arrays (one for the match pattern and other for the replace pattern), then use the respective array members in the search-replace, like so:

match=(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
replace=(01 02 03 04 05 06 07 08 09 10 11 12);

for FILE in *.pdf;
do 
    for i in {0..11};    
    do
        echo ${match[i]} ${replace[i]};  # Note, see below
        echo ${FILE/${match[i]}/${replace[i]}};
    done
done

The line marked Note prints Jan 01, Feb 02, etc. as expected, but using them as the search or replace pattern in the next line has no effect, and $FILE gets printed as it is.

I have also confirmed that both of the following work:

${FILE/Jan/01};

foo=Jan;
bar=01;
${FILE/$foo/$bar};

Is it possible to use an array member as the pattern, as I attempted above? If so, what is the correct syntax? If not, what other options do I have to solve this problem?

5
  • 1
    What version of bash are you using? Check $BASH_VERSION. Your script works on my Mac running 3.2.48(1)-release and my Linux running 3.2.25(1)-release. Commented Oct 5, 2013 at 17:06
  • 1
    Instead of echo can you try: rename -n "s/${match[i]}/${replace[i]}" Commented Oct 5, 2013 at 17:11
  • Works for me in bash 4.2.45(0) as well. Doesn't seem like the best way though; I'll suggest another alternative as an answer. Commented Oct 5, 2013 at 20:27
  • While you're at it, perhaps change to MSB order; yyyy-mm? Commented Oct 6, 2013 at 6:08
  • @tripleee That's right! The main reason I am doing this rename is to allow the files to be sorted easily. I didn't mention it here because it wasn't relevant to the problem. Commented Oct 6, 2013 at 6:14

3 Answers 3

2

BASH supports associative arrays which would be a good fit for your use case:

# Initialize the associative array
declare -A months

# Fill in the array with the month string as the key and the zero-padded month number as the value
num=0
for m in Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec; do
  ((num++))
  months[$m]=$(printf '%02d' $num)
done

# Test your examples
for file in Foo_Jan_2013.pdf Bar_Feb_2012.pdf Foo_Mar_2013.pdf Bar_Mar_2013.pdf; do
  month=$(cut -d_ -f2 <<<"$file")
  new_name="${file/$month/${months[$month]}}"
  echo "$file -> $new_name"
done

Output:

Foo_Jan_2013.pdf -> Foo_01_2013.pdf
Bar_Feb_2012.pdf -> Bar_02_2012.pdf
Foo_Mar_2013.pdf -> Foo_03_2013.pdf
Bar_Mar_2013.pdf -> Bar_03_2013.pdf
Sign up to request clarification or add additional context in comments.

1 Comment

It's worth pointing out that associative arrays were added in bash 4.0.
2

For situations like this my favorite trick is to generate a series of commands with (e.g.) sed:

num=0
subst=
for month in Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec; do
    num=$(($num + 1))
    case $num in ?) num2=0$num;; *) num2=$num; esac
    subst="$subst -e 's/\(.*\)_${month}_\(.*\)/mv & \1_${num2}_\2/'"
done
# echo "sed $subst" # debug
# echo Foo_Jan_2013.pdf | eval sed $subst # debug
ls *.pdf | eval sed $subst # debug
# ls *.pdf | eval sed $subst | sh # once it all works

(this version avoids bash-specific code). I left in the debug-echo steps, too, and left the final part commented-out.

Comments

1

I put together the answers by iscfrc, torek, and the comment by devnull, and solved the problem as follows:

# Initialize an associative array with (month name, number) pairs
declare -A map=([Jan]=01 [Feb]=02 [Mar]=03 [Apr]=04 [May]=05 [Jun]=06 
[Jul]=07 [Aug]=08 [Sep]=09 [Oct]=10 [Nov]=11 [Dec]=12);

# For each month name, use 'rename' command on filenames having the month name
for m in ${!map[@]};
do
    eval rename \'s/$m/${map[$m]}/\' *$m*.pdf
done

With this approach, each filename is matched only against the pattern that it does contain (not all 12 month names), and the nested loop structure in my question is avoided. It also doesn't use any additional pipes, and works even when the "separator" is different from _.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.