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?

4 Answers 4

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
5
  • Will work in zsh but not in bash or ksh93 where numbers with a leading 0 are considered as octal. Nov 9, 2013 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, 2013 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. Nov 10, 2013 at 19:41
  • @1_CR, have you tested with 008 in the mapfile? Nov 10, 2013 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, 2013 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.

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .