0

I have a directory containing hundreds of image files, each named differently, such as:

abdogf.png

abcatf.png

abhorsef.png

I have created a changenames.csv file containing two columns with oldstring in the first column and newstring in the second such as:

"dog","woof"

"cat","miaow"

"horse","neigh"

These strings are currently in quotation marks, as shown.

I would like to invoke a bash command or .sh script from the command line to replace each oldstring substring with each newstring substring in the directory's filenames (not within file contents), such that the directory newly contains files called:

abwooff.png

abmiaowf.png

abneighf.png

instead of the original files.

I have tried various solutions such as https://superuser.com/questions/508731/find-and-replace-string-in-filenames/508758#508758 and How to find and replace part of filenames from list without success.

For example, I have tried invoking the following within the directory with the files:

#!/bin/bash
inputfile=${1}
while read line
do
    IFS=',' read -a names <<< "${line}"
    for file in `find . -maxdepth 1 -type f -name "*${names[0]}*"`; do
        rename "s/${names[0]}/${names[1]}/" *
    done
done < ${inputfile}

using the command line command test.sh changenames.csv.

This produces no error but makes no changes to the filenames.

I have also tried this solution https://stackoverflow.com/a/55866613/10456769 which generated an error in which @echo was not a recognised command.

Thank you in advance for any help.

7
  • what is the actual code you've tried and the (incorrect) results you're receiving? Commented Feb 6, 2020 at 23:06
  • #!/bin/bash inputfile=${1} while read line do IFS=',' read -a names <<< "${line}" for file in find . -maxdepth 1 -type f -name "*${names[0]}*"; do rename "s/${names[0]}/${names[1]}/" * done done < ${inputfile} Code above. Invoking from command line within the directory in which files are to be changed as test.sh changenames.csv, and trying the same command from within the parent directory of where the files are stored. No error doing this but just no change made. (I have first used chmod to allow me to run the .sh script.) Commented Feb 6, 2020 at 23:59
  • 2
    go ahead and update your question with all of that, don't bury details in comments Commented Feb 7, 2020 at 0:01
  • I then tried @echo off setlocal enabledelayedexpansion for /f "tokens=1,* delims=," %%i in (myfile.csv) do ( set "search=%%i" set "replace=%%j" call :fix ) exit /b :fix for %%a in (!search!.wav) do ( set "file=%%a" set "file=!file:%search%=%replace%!!" echo ren "%%~fa" "!file!" ) Error ./test2.sh: line 1: @echo: command not found ./test2.sh: line 2: setlocal: command not found ./test2.sh: line 3: syntax error near unexpected token "tokens=1,* delims=,"' ./test2.sh: line 3: for /f "tokens=1,* delims=," %%i in (myfile.csv) do (' Commented Feb 7, 2020 at 0:03
  • 1
    Add a couple echo statements to verify that your rename command is what you are expecting. Try running your rename command that was printed to ensure it works. Debug :-) Commented Feb 7, 2020 at 0:34

2 Answers 2

1

You need to strip the double quotes off at first. The code tries to find files such as *"cat"* which do not exit.
Moreover you do not need to execute the find command. You are not using the variable file at all.
Would you please try the following:

while IFS=',' read -r old new; do
    old=${old//\"/}     # remove leading and trailing double-quotes
    new=${new//\"/}     # same as above
    rename "s/$old/$new/" *
done < "$1"
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for this. This still doesn't seem to change anything. Could you clarify how I invoke this to read on a particular csv with the (old,new) pairs? I have written the above code in a shell test.sh and used the command ./test.sh changenames.csv from the bash command line within the directory containing the files I want to rename, and also containing the test.sh and changenames.csv file. Thank you!
At start let's see if the rename command works on the command line. Please make sure you have files abdogf.png, abcatf.png and abhorsef.png in the current directory, execute rename "s/dog/woof/" *. If the filename of abdogf.png is properly changed to abwooff.png, then it is working. Rename it back to the original name with mv command, and let's move to the next step.
As a next step let's examine if the csv file is properly read. Please modify my script test.sh by adding a line echo "s/$old/$new/" just below the rename line. Then execute ./test.sh changenames.csv. If it is working well, the three lines s/dog/woof/, s/cat/miaow/ and s/horse/neigh/ will be displayed on the terminal.
If the output looks like //dog/woof, //cat/miaow and //horse/neigh, your changenames.csv will contain a DOS "\r" character at the end of lines. If so, please convert the file with dos2unix by executing dos2unix changenames.csv. Then everything will be okay.
0

The IFS=',' read -a names <<< "${line}" does not remove " from the input. Your filenames do not have " in them, so you have to remove them too.

Backticks ` are discouraged. Don't use them. Use $(....) instead.

"for file in `" is as bad as for file in $(cat) - it's a common bash antipattern. Don't use it - you will have problems with elements with spaces or tabs. Use while IFS= read -r line to read something like by line.

There is a problem with rename, there are two common versions of rename - GNU rename and perl rename. Your script seems to aim the perl version - make sure it is the one installed.

Let rename do the rename - there is no need for for file in find here.

If you do while read line and then IFS=, read <<<"$line" is duplicating the work, just do while IFS=, read -a names; do from the beginning.

So you could do:

# split the input on ','
while IFS=',' read -r pre post; do
    # remove quotes
    pre=${pre//\"/}
    post=${post//\"/}
    # do the rename
    rename "s/${pre}/${post}/" *
done < ${inputfile}

I think I would do the following script that uses sed:

# find all files in a directory
find . -maxdepth 1 -type f |
# convert filenames into pairs (filename,new_filename) separated by newline
sed "
   # hold the line in hold space
   h
   # replace the characters as in the other file
   $(
      # generate from "abc","def" -> s"abc"def"g
      sed 's@"\([^"]*\)","\([^"]*\)"@s"\1"\2"g@' changenames.csv
   )
   # switch pattern and hold space
   x
   # append the line
   G
   # remove the line if substitute is the same
   /^\(.*\)\n\1$/d
" |
# outputs two lines per each filename:
# one line with old filename and one line with new filename
# so just pass that to mv
xargs -l2 echo mv -v

and a one liner:

 find . -maxdepth 1 -type f | sed "h;$(sed 's@"\([^"]*\)","\([^"]*\)"@s"\1"\2"g@' changenames.csv);x;G; /^\(.*\)\n\1$/d" | xargs -l2 echo mv -v

With the following recreation of files structure:

touch  abdogf.png abcatf.png abhorsef.png
cat <<EOF >changenames.csv
"dog","woof"
"cat","miaow"
"horse","neigh"
EOF

The script outputs on repl:

mv -v ./abdogf.png ./abwooff.png
mv -v ./abcatf.png ./abmiaowf.png
mv -v ./abhorsef.png ./abneighf.png

2 Comments

Thank you for this. I have tried the one liner and can see it correctly rewriting the new filenames on the command output as you suggest here: # outputs two lines per each filename: # one line with old filename and one line with new filename However, the original files still exist in the folder without the new ones when I type ls. Do I need to do something else to actually get the new filenames written out into the directory to replace the original files?
You need to remove the echo in xargs -l2 echo mv -v to really move the files.

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.