3

Using bash, how I can batch rename a large number of files, where I need to construct a new name for each file, based on its existing name and a 'contextual map' - don't know what else to call it – of name fragments.

I have a lot of files in like this:

001.jpg
003.jpg
004.jpg
007.jpg
...

and a map like this:

001,white
003,grey
004,red
007,green
...

And now I want to rename all files, so that 001.jpg becomes 001-white.jpg etc..

How can I do this?

2

bash4: First read the mappings into an array

declare -A arr2
while IFS=, read -r -a arr; do
    arr2[${arr[0]}]="${arr[1]}";
done < mapfile.txt

And then rename

for f in [0-9][0-9][0-9].jpg; do
    if [[ "${arr2[${f%.*}]+_}" ]]
    then
        mv -- "${f}" "${f%.*}"-"${arr2[${f%.*}]}"."${f##*.}";
    else
        mv -- "${f}" "${f%.*}"-default."${f##*.}";
    fi
 done
  • Will work in zsh but not in bash or ksh93 where numbers with a leading 0 are considered as octal. – Stéphane Chazelas Nov 9 '13 at 20:15
  • @StephaneChazelas, i tested this on GNU bash, version 4.2.25 before posting, it works. I am guessing this is because arr2 is considered an associative array by default under bash 4 so the index is not considered an "arithmetic context"? – iruvar Nov 10 '13 at 16:15
  • If a file isn't in the map, it's renamed to e.g. 042-.jpg. You may want to add a test for this. – Gilles Nov 10 '13 at 19:41
  • @1_CR, have you tested with 008 in the mapfile? – Stéphane Chazelas Nov 10 '13 at 20:14
  • @StephaneChazelas, ah I see what you mean. I seem to be able to work around this by explicitly declaring arr2 an associative array. Tagging this a bash4 solution – iruvar Nov 10 '13 at 20:56
2

You can simply execute

join -t. -j1 <(tr , . < map) filelist | sed 's/\./-/' | paste filelist - | xargs -L1 mv

where map is the contextual map and filelist is the list of files. The join will split on . and uses the first field as a join value; both lists must be sorted. paste will then glue each line of filelist with the newly generated filename together in one line, while xargs then calls mv on each of those lines.

1

Here you go:

for file in [0-9][0-9][0-9].jpg;do
    name="${file%%.*}" # Remove extension
    map="$(grep "$name" map_file | cut -d',' -f 2)"
    echo mv "$file" "$name-$map".jpg
done

This assumes the "map" is in the file map_file. In recent versions of Bash, you can also transform this file into an associative array and use that for convenience (instead of going through the map_file for each new file you want to rename).

By the way, this will only echo the mv commands. Remove the echo when you feel that everything is in order to actually do the renaming.

1
< map sed 's/\([^,]*\),\(.*\)/mv -- "\1.jpg" "\1-\2.jpg"/' | sh -x

(that assumes the map file doesn't contain ", $, backslash or backtick characters.

  • this looks interesting, very terse, can you explain how this works? – mikkelbreum Nov 10 '13 at 0:09
  • If the map mentions a non-existent file, you'll get an error message, which is likely not desired. – Gilles Nov 10 '13 at 19:42

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.