2

I have a large number of files I want to rename. Each filename contains a number (xxx): FIXED_PART_xxx_UNIQUE_PART.

I also have a text file, which has a number and a string in each line:

001 string001
002 string002
...
112 string112

there is an entry for each file, but not necessarily a file for each entry. I want to rename each file by parsing the xxx part and replacing it with the string from the file.

What is the best way to do that?

edit: the actual file names look like this:

Berlin_Ost_034_some_variable_text.jpg

so the fixed part contains also an underscore in this case, but it is the same for each folder. The xxx part consists only of numbers.

the output would be for this file

Berlin_Ost_Sommer_some_variable_text.jpg

and I am talking about 200 files

The text file contains a line: 034 Sommer

which is why the 034 part in the file gets replaced by Sommer

The file contains all sorts of strings, but only characters and numbers and german umlauts. The first part is always a number. Here is a part from one of the files I am using

033 Winter
034 Sommer
035 Herbst
036 Frühling
037 Abends.kurz.vor.Sonnenaufgang

I am using linux

5
  • 3
    Please edit your question and show us some actual file names because the details are important. make sure to also show what output you are expecting from the specific examples you give. Will this xxx always be digits? Also, what operating system are you using? Different systems come with different tools.
    – terdon
    Commented Dec 23, 2021 at 13:52
  • 2
    Can we assume that the xxx is the third underscore-delimited item in each filename? If not, what criteria can we use to identify and extract it? How many files is "a large number" - tens, hundreds, thousands, more? All in the same directory or spread across several directories? Commented Dec 23, 2021 at 13:58
  • 1
    Also please edit the question adding a real sample of the text file. Commented Dec 23, 2021 at 14:08
  • Please make sure you use examples that we can understand. Where did this Sommer come from? Presumably, there will be a line with Sommer in the text file, but since we cannot see it, that doesn't help. We need to see an actual example (or a few) because there are all sorts of details that can affect the solutions. And we really need to know what operating system you are using.
    – terdon
    Commented Dec 23, 2021 at 14:20
  • Umlouts, ah. So this isn't ACII text. OK, can the strings ever contain spaces? How about the file names?
    – terdon
    Commented Dec 23, 2021 at 15:03

4 Answers 4

3

With zsh and assuming the strings don't contain space or tab characters:

autoload -Uz zmv
typeset -A mapping=($(<mapping.txt))
zmv -n '(*_)([0-9](#c3))(_*.jpg)' '$1${mapping[$2]-$2}$3'

Remove the -n (dry-run) if happy.

For file names that contain more than one sequence of 3 digits between underscores, the right-most one will be replaced. Using ${mapping[$2]-$2} ensures that if that sequence of digits is not found in the mapping, then it will be left asis.

If all files have the same prefix, you can also do:

zmv -n '(Berlin_Ost_)([0-9](#c3))(_*.jpg)' '$1${mapping[$2]-$2}$3'

If the strings may contain spaces or blanks (other than at the beginning or end), you can build the associative array as:

typeset -A mapping
while read -r key value; do mapping[$key]=$value; done < mapping.txt

instead.

To replace all occurrences of 3 digits in file names, you can do:

zmv -n '*.jpg' '${f//(#m)[0-9](#c3)/${mapping[$MATCH]-$MATCH}'

In that case, we don't look for underscore on either sides. We could do:

zmv -n '*.jpg' '${f//(#b)_([0-9](#c3))_/${mapping[$match[1]]-$match[1]}'

But while that would replace both 001 and 002 in foo_001_bar_002_baz.jpg, that woudn't in foo_001_002_bar.jpg as the second _ would be included in the first match and so couldn't be part of later matches.

2

You can iterate across each entry in your list of strings, renaming any matching files. This can be squashed into a single line but I've left it open here so I can append comments:

while IFS=' ' read -r key value                            # Extract the key and value
do
    for file in *_"$key"_*                                 # Loop across all matches
    do
        [ -f "$file" ] &&
            mv -- "$file" "${file/_${key}_/_${value}_}"    # Replace key with value
    done
done <keysandvalues                                        # Read the file of matches

The replacement expression will replace only the first occurrence of the key, and it must be bounded by underscore on both sides. Neither the key nor the value may contain /.

A side-effect of iterating across the file of replacements is that if you have a file name containing two replacement strings (for example, Berlin_033_other_035_text.jpg) then both will be replaced as they are matched (resulting here in Berlin_Winter_other_Herbst_text.jpg). Moving renamed files to a separate directory could work around this issue if it's a problem.

3
  • Is necessary a loop of the files if the key is unique? Just an observation. Commented Dec 23, 2021 at 15:26
  • @schrodigerscatcuriosity I didn't see an explicit statement of uniqueness in the question so I assumed there might be zero or more rather than zero or one. (I did read "there is an entry for each file, but not necessarily a file for each entry" but I didn't see anything that said there would be at most one file per entry.) Commented Dec 23, 2021 at 15:35
  • 1
    Right, re-reading the question I realize that. I just assumed the file names where unique in its keys. Commented Dec 23, 2021 at 15:38
2

Here's a solution assuming _some_variable_text doesn't contain numbers:

$ for i in *.jpg; do
    s="$(grep "${i//[^0-9]/}" file.txt)"
    echo mv -- "$i" "${i//[0-9]*/}${s//* /}_${i//*[0-9]_/}"
    # to actually perform the command remove the echo
    # do a backup first
  done
mv -- Berlin_Ost_034_some_variable_text.jpg Berlin_Ost_Sommer_some_variable_text.jpg

  • Loop through the files.
  • s="$(grep "${i//[^0-9]/}" file.txt)" extract the key;
  • mv -- "$i" "${i//[0-9]*/}${s//* /}_${i//*[0-9]_/}" rename the file using parameter substitution to get the different parts of the file name.
-1

One line

prefix="foo" ls | grep -oP "(?<=${prefix}_).*" | xargs -I % sh -c 'mv ${prefix}_% ${prefix}_$( echo % | cut -d"_" -f1 | grep -f - filenames.txt | cut -d" " -f2 )_$( echo % | cut -d"_" -f2- ) '

where foo is the prefix (FIXED_PART) and filenames.txt the file with the names...

explanations:

it gets all the filenames starting with prefix in the curent directory, but match only the part after the prefix grep -oP "(?<=${prefix}_).*". For each of these lines (1) ,it renames file prefix+(1) to prefix+(search in the file for the first part of (1), and get the corresponding string)+remaining part of (1)

Et voilà

2
  • 2
    Welcome, did you test this? Commented Dec 23, 2021 at 15:12
  • yep. i create files with : seq -w 1 112 | xargs -I % touch foo_%_${RANDOM} and the file of names : paste -d ' ' <(seq -w 1 112) <(cat /dev/urandom | tr -cd 'a-zA-Z\n' | strings -n10 | cut -c-10 |head -n 112) > file.txt it works
    – Dotsh
    Commented Dec 23, 2021 at 15:19

You must log in to answer this question.

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