I have files with names and directory paths as follows:

t10n2/data/file.dat0, t10n2/data/file.dat1, t10n2/data/file.dat2, ...

I wish to change the names of the files using an arithmetic operation which involves the variables $1 and $2 in t10n$1 and dat$2, such that the file names are changed to file.dat$(($1*5 + $2)).

In particular, I guess what I want to know is how to use the output of a wildcard like ! or * as a variable in an arithmetic operation? The rest I can do.

  • the basic command are find, mv etc. – RomanPerekhrest Oct 5 '17 at 15:35
  • I had not asked the write question. I just edited it. – ap21 Oct 5 '17 at 15:57
  • Achievement unlocked: homework automation. – Satō Katsura Oct 5 '17 at 16:00

With Perl rename:

prename -n 's!t10n(\d+)/data/file.dat\K(\d+)!$1*5+$2!e' t10n*/data/file.dat*

Remove -n when you're happy with the result.

  • Can you please tell me what the -n does? And are there no non-Perl alternatives? I am just not used to the syntax. – ap21 Oct 5 '17 at 16:02
  • -n means "dry run". Yes, there are two widespread non-Perl implementations of rename. The Perl one comes from Unicode::Tussle. – Satō Katsura Oct 5 '17 at 16:07

Using bash:

for file in t10*/data/file.dat*
  if [[ $file =~ (t10n([[:digit:]]+)/data/file.dat)([[:digit:]]+) ]]
    echo mv -- "$file" "${BASH_REMATCH[1]}$(( ${BASH_REMATCH[2]} * 5 + ${BASH_REMATCH[3]} ))"

The heavy lifting is done by the =~ regular-expression operator in the [[ test operation. There are three parenthesized expressions in order to grab the elements we're interested in:

  1. the bulk of the filename, except for the trailing dat number
  2. the t10n number
  3. the dat number

If the file matches the pattern, then the resulting values are in the BASH_REMATCH array, so we use those to calculate the new filename.

Remove the echo if the results look correct.

Sample input:

mkdir -p t10n2/data t10n3/data
touch t10n2/data/{file.dat0,file.dat1,file.dat2}
touch t10n3/data/{file.dat0,file.dat1,file.dat2}

Sample output:

mv -- t10n2/data/file.dat0 t10n2/data/file.dat10
mv -- t10n2/data/file.dat1 t10n2/data/file.dat11
mv -- t10n2/data/file.dat2 t10n2/data/file.dat12
mv -- t10n3/data/file.dat0 t10n3/data/file.dat15
mv -- t10n3/data/file.dat1 t10n3/data/file.dat16
mv -- t10n3/data/file.dat2 t10n3/data/file.dat17

With zsh:

autoload zmv # best in ~/.zshrc
zmv -n 't10n(<->)/data/file.dat(<->)' '$f:r.dat$(($1 * 5 + $2))'

(remove -n (for dry-run) when happy).


find + bash + sed solution:

find . -type f -path "*t[0-9]*n[0-9]*/data/file.dat[0-9]*" -exec bash -c 'f=$1; 
      read v1 v2 < <(sed -E "s#.*/t[0-9]+n([0-9]+)/data/file\.dat([0-9]+)\$#\1 \2#" <<<"$f"); 
      mv "$f" "${f/.dat[0-9]*/.dat}$(($v1*5+$v2))"' x {} \;

  • read v1 v2 ... - read the crucial numbers from t10n<number> and dat<number> into v1 and v2 variables respectively
  • Thanks for the answer. I preferred the other one though, since it was shorter. – ap21 Oct 5 '17 at 16:24
  • Another way to pass the pathname from find to bash is with command line arguments: find ... -exec bash -c 'f=$1; ...' x {} \; -- here "x" will become $0 and the filename gets $1 – glenn jackman Oct 5 '17 at 16:35
  • Also, the bash manual uses $((...)) not $[...] -- I think the latter syntax is deprecated, but I can't find that documented. – glenn jackman Oct 5 '17 at 16:36
  • @glennjackman, edited, but I haven't encountered cases when $[...] syntax would not work ... yet – RomanPerekhrest Oct 5 '17 at 16:41
  • That is true. However, since $[] is not documented in the manual, I think it's bad practice to use it in a "teaching" context. – glenn jackman Oct 5 '17 at 16:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.