Renaming files with multiple variables in the file names

704 views Asked by At

I have multiple files with this format: this-is-text_r_123.txt and this-is-text.txt.

What I would like to do (preferable using a for loop) is to rename all this-is-text.txt files to their corresponding this-is-text_r_123.txt matches but have an i instead of the r in the file name. Considering that this-is-text is a random text (different from one file to another) and the 123 in the example above is any combination of 3 numbers. All files are in one directory.

I tried with mv and rename but I wasn't successful

I've searched and reviewed all the file renaming questions here but none matched my case

2

There are 2 answers

1
rr- On BEST ANSWER

I changed the technology to Python to demonstrate how to do this in a language more convenient than bash:

#!/usr/bin/python3
import glob
import re

text_files = glob.glob('*.txt')

#divide the files into two groups: "normal" files without _r and "target" files with _r
normal_files = {}
target_files = {}
for path in text_files:
    #extract "key" (meaning part of file name without _r or _i)
    #as well as whether the file contains _r or _i, or not
    #using regular expressions:
    result = re.match('(?P<key>.*?)(?P<target>_[ri]_?\d*)?\..*$', path)
    if result:
        if result.group('target'):
            target_files[result.group('key')] = path
        else:
            normal_files[result.group('key')] = path

print(normal_files)
print(target_files)

#now figure out how to rename the files using the built dictionaries:
for key, path in normal_files.items():
    if key in target_files:
        target_path = target_files[key].replace('_r', '_i')
        print('Renaming %s to %s' % (path, target_path))

For following set of files:

asd.txt
asd_r_1.txt
test test.txt
test test_r2.txt
another test_i_1.txt

this script will produce:

{'test test': 'test test.txt', 'asd': 'asd.txt'}
{'test test': 'test test_r2.txt', 'another test': 'another test_i_1.txt', 'asd': 'asd_r_1.txt'}
Renaming test test.txt to test test_i2.txt
Renaming asd.txt to asd_i_1.txt

You should be able to move files with this.

As you see, it works.

If you really need doing this in bash, it should be easy to port using sed or awk.

9
rr- On

Interpretation #1

If you want to rename *.txt to their _r_<NUMBER>.txt counterparts and you're sure only one such file exists for each .txt file, you can use following:

for x in *.txt
do
    if [[ "$x" != *_r_* && "$x" != *_i_* ]]; then
        y="${x%.*}"
        echo "$x" "${y}_r_"*
    fi
done
  1. We loop through all *.txt files.
    1. We check if it isn't target _r_*.txt nor to-be-renamed-to _i_*.txt file.
    2. If it is, we ignore it. If it isn't, we:
      1. Extract the base file name, without extension, to $y.
      2. Output the source file name and proposed target file name, relying on * glob star operator. If multiple files are matched, it will print all of them. If there are none, it will print only source file name. Depending on these circumstances, you might either move the file or keep it.

To replace _r_ with _i_ in variable $z, you might want to use z=${z/_r_/_i_}. This will prove useful in point 1.2.2.

Interpretation #2

To move each *.txt file and assign it a number:

i=0
for x in *.txt
do
    let i+=1
    y="$(echo "$x"|sed 's/\(\.[a-z]*\)$/_r_'"$i"'\1/')"
    echo "$x" "$y"
done
  1. First we declare variable i and set it to 0.
  2. Then we loop through all *.txt files.
    1. Then we increase $i it by 1 using let i+=1.
    2. Then we get new file name by using sed, in which:
      1. we replace (s/A/B/) the file extension (.[a-z]*$) with _r_,
      2. followed by $i,
      3. followed by the captured file extension (\1) by the brackets \(\) in the left hand side of the s/// operator.
      4. We wrap normal text with ' and variables with ". Note how quoting is changed twice in the expression.
    3. Then we echo the original and new file names rather than moving so that we can verify if the results are correct.

Seeing it in action:

rr-@herp:~$ i=0; for x in *.txt; do let i+=1; y="$(echo "$x"|sed 's/\(\.[a-z]*\)$/_r_'"$i"'\1/')"; echo "$x" "$y"; done
mm todo.txt mm todo_r_1.txt
mm.txt mm_r_2.txt

Notes

  1. If you need to verify if $i-th file already exists, you can use if [ -f $target ].
  2. You could use find to find the files, but it's more complex and you should search on the web how to use find with for loops.