1

I wish to iterate over all directories where my bash script is stored and cd into each directory to then rename my filenames by substituting a substring for another. Once done renaming the file save the file to the same directory it was originally in.

Here is my script:

#!/bin/bash 
# loop over all directories
for dir in `ls -d */`; do

    # cd into directory and print directory -- Sanity check

    ( cd "$dir" && echo "$dir" );

    # loop over files within directory


    for file in $dir/*; do

        echo $file;

        # assign var 'fname' to file
        fname1 = $file;
        # assign variable 'fname2' to new filename, replace 'ops' for 'fcst_ops'
        fname2=`echo $file | awk '{sub("ops","fcst_ops"); print $0}'` ;

        # rename original filename to new filename(fname2) in same directory
        mv $fname1 $fname2;
        #echo "$(basename "$file")"; WORKS
        echo $fname2;
        done
    done

Current issues: fname1: command not found && fname2 variable name is not correct.

I've tried assigning my fname1 variable in many ways but bash still throws an error.

I've also tried different methods to get the filename variable correctly but no luck.

This issue is a bit different than the others since I am iterating over directories and renaming the filename one file at a time w/r to each directory.

I wish to save each filename in its respective directory after substitution of 'ops' for 'fcst_ops'.

  • 1
    Please take a look at [How to create a Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve). – Cyrus Jul 29 '23 at 00:30
  • 4
    Copy/paste your code with `#!/bin/bash` as the first line into https://shellcheck.net . Fix the flagged problems. If your code still isn't working, then update the body of your question with the corrected code. If your code starts working, please delete this as it is almost certainly a duplicate (at it's core). Good luck. – shellter Jul 29 '23 at 00:46
  • 1
    It looks like the main problem is [spaces around the `=` in an assignment](https://stackoverflow.com/questions/2268104/) (don't do that, it doesn't work). This is one of the problems that [shellcheck.net](https://www.shellcheck.net) will point out. Also, I'd replace that `awk` thing with `fname2=${fname1/ops/fcst_ops}`, and I *always* recommend using `mv -i` or `mv -n` for bulk/automated move/renames, to avoid deleting files if there's a name conflict. Finally, if you do need additional help, please use [code block format](https://meta.stackoverflow.com/q/251361), so your script is readable. – Gordon Davisson Jul 29 '23 at 01:26
  • This is extremely helpful, shellcheck.net is new to me but extremely helpful, thank you! The issue was indeed the spaces for assignment 'fname1'. – matthewfern Jul 29 '23 at 06:18
  • Voting to close as a typo. Glad you learned about a new tool and solved your problem. Good luck. – shellter Jul 29 '23 at 16:48

3 Answers3

3

Example code (not tested, just written down) without cd to directory.

# for each file in each directory
for path in */*
do
    # extract name (remove all before last "/")
    name="${path##*/}"

    # rename only if name not contains "fcst_ops"
    [[ $name =~ fcst_ops ]] || name="${name/ops/fcst_ops}"
   
    # create output filename
    dest="${path%/*}/${name}"

    # mv only if a new filename was created
    [[ "$path" != "$dest" ]] && mv -n "$path" "$dest" && echo "$dest"
done
Wiimm
  • 2,971
  • 1
  • 15
  • 25
1

There are several syntax errors in your script (for instance spaces around the = assignment operator).

When working with sets of files the find utility is frequently handy:

find . -maxdepth 2 -mindepth 2 -type f -name '*ops*' \
  -execdir bash -c 'echo mv "$1" "${1/ops/fcst_ops}"' _ {} \;

This searches files (-type f) named *ops* (-name '*ops*') in all subdirectories of the current directory (. -maxdepth 2 -mindepth 2) and executes the bash script mv "$1" "${1/ops/fcst_ops}" from inside the subdirectory (-execdir) with _ as argument 0 and the file name as argument 1.

The script uses bash pattern substitution to replace the first occurrence of ops with fcst_ops in the first argument ("${1/ops/fcst_ops}").

As it is it only echoes the command that would be executed. If it looks fine simply remove echo and run again. Demo:

$ mkdir -p {a..d}; for i in {a..d}; do touch "$i/$i.ops.$i"; done
$ find . -maxdepth 2 -mindepth 2 -type f -name '*ops*' \
  -execdir bash -c 'echo mv "$1" "${1/ops/fcst_ops}"' _ {} \;
mv ./a.ops.a ./a.fcst_ops.a
mv ./c.ops.c ./c.fcst_ops.c
mv ./d.ops.d ./d.fcst_ops.d
mv ./b.ops.b ./b.fcst_ops.b

In case you don't want multiple renaming (fcst_ops -> fcst_fcst_ops) and you have already renamed some files, you can ask find to not match fcst_ops (! -name '*fcst_ops*'):

find . -maxdepth 2 -mindepth 2 -type f -name '*ops*' ! -name '*fcst_ops*' \
  -execdir bash -c 'echo mv "$1" "${1/ops/fcst_ops}"' _ {} \;
Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51
  • Interesting, this seems like it may work but unfortunately doesn't. see the echo output of a file that contains fcst_fcst_ops when I really wanted fcst_ops. Would you mind elaborating a bit on how this method goes into each subdirectory and substitutes the filenames that contain 'ops' for 'fcst_ops' without that duplication as mentioned above? – matthewfern Jul 29 '23 at 07:21
  • 1
    If you see a `fcst_fcst_ops` it is because you already renamed some files and `fcst_ops` contains `ops`so, per your specification, it shall be renamed `fcst_fcst_ops`... If you want to skip the files which name already contains `fcst_ops` please edit your question and add this information. – Renaud Pacalet Jul 29 '23 at 09:23
  • And see the last part of my edited answer. – Renaud Pacalet Jul 29 '23 at 09:35
0

rename my filenames by substituting a substring for another

If you are not strictly limited to bash consider using rename, to use it describe substitution using sed syntax and provide files to be renamed, for example if I have catalog photos and want to change .JPG endings into .jpeg I could do that by doing

rename s'/[.]JPG$/.jpeg/' photos/*.JPG
Daweo
  • 31,313
  • 3
  • 12
  • 25