TransferOrbit
TransferOrbit

Reputation: 227

How to fix shell script that works at the command line? Script uses `mv`, quotation marks, parameter expansion, and variables

I have a set of files in a directory. Some of the filenames contain an eight-digit date, like so:

test_file_20220101.txt
...
test_file_20220110.txt

I want to use a shell script to rename them such that the dates have dashes, like so:

test_file_2022-01-01.txt
...
test_file_2022-01-10.txt

I have some shell code which works (mostly) fine at the command line (once echo is removed*):

for f in ./*; 
do suffix="${f##*[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]}"; 
number="${f%"$suffix"}"; 
number="${number##*[\!0-9]}"; 
year="${number:0:4}"; 
month="${number:4:2}"; 
day="${number:6:2}"; 
echo mv "$f" "${f/$number/$year-$month-$day}"; 
done

However, when I add #!/bin/sh, #!/bin/bash, or #!/bin/zsh at the start, save it as a script (with execution permissions), and run the script, it doesn’t effect the filename change, and I get this in stdout:

echo mv ./test_file_20220110 ./test_file_20220110

* Remove echo changes nothing; the undesired results are the same with the shell script. I don’t believe echo is the problem.

I’ve also tried, without success: replacing all with or all with and removing all quotation marks. All combinations produce the same erroneous script output. The output of running the script with set -v added doesn’t help me, but it is:

+ for f in './*'
+ suffix=
+ number=./test_file_20220101
+ number=
+ year=
+ month=
+ day=
+ echo mv ./test_file_20220101 ./test_file_20220101
mv ./test_file_20220101 ./test_file_20220101
... [etc.]

Configuration:


Separately, and as an additional improvement, I’d like to fix the fact that some files which don’t have the expected eight-digit pattern end up being renamed to something like --other_filename.txt. I use the following command separately, after using the above command at the CLI, to fix this successfully.

for f in *; do echo mv -- "$f" "${f##--}"; done

It would be nice if this “cleanup” command could be in the same script as above as a last “cleanup” sweep.

Upvotes: 1

Views: 406

Answers (2)

pjh
pjh

Reputation: 8209

This Shellcheck-clean code demonstrates another way to do it:

#! /bin/bash -p

date_rx='([[:digit:]][[:digit:]][[:digit:]][[:digit:]])([[:digit:]][[:digit:]])([[:digit:]][[:digit:]])'
date_file_rx="(.*)${date_rx}(.*)"

for file in *; do
    if [[ $file =~ $date_file_rx ]]; then
        prefix=${BASH_REMATCH[1]}
        year=${BASH_REMATCH[2]}
        month=${BASH_REMATCH[3]}
        day=${BASH_REMATCH[4]}
        suffix=${BASH_REMATCH[5]}
        echo mv -n -v -- "$file" "${prefix}${year}-${month}-${day}${suffix}"
    fi
done

Upvotes: 0

John Bollinger
John Bollinger

Reputation: 181199

The set -v output may not help you, but it tells me that the script goes sideways when trying to extract the number from the filename. In bash, at least, that's because you have quoted the ! in your bracket expression. That is, [\!0-9] matches one character from the set '!', '0', '1', ... '9', which is not what you want.

You should observe the same at the command line, and if you don't, then I cannot explain that.

This variation on your script works for me:

#!/bin/bash

for f in ./*; do
  suffix=${f##*[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]}
  number=${f%"$suffix"}
  number=${number##*[!0-9]}
  year=${number:0:4}
  month=${number:4:2}
  day=${number:6:2}
  echo mv "$f" "${f/${number}/${year}-${month}-${day}}"
done

(Changes other than the correction to the bracket expression are stylistic, not functional.)

Upvotes: 2

Related Questions