1

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.

3
  • the basic command are find, mv etc. Commented Oct 5, 2017 at 15:35
  • I had not asked the write question. I just edited it.
    – ap21
    Commented Oct 5, 2017 at 15:57
  • Achievement unlocked: homework automation. Commented Oct 5, 2017 at 16:00

4 Answers 4

3

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.

2
  • 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
    Commented Oct 5, 2017 at 16:02
  • -n means "dry run". Yes, there are two widespread non-Perl implementations of rename. The Perl one comes from Unicode::Tussle. Commented Oct 5, 2017 at 16:07
3

Using bash:

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

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
1

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).

0

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
5
  • Thanks for the answer. I preferred the other one though, since it was shorter.
    – ap21
    Commented Oct 5, 2017 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 Commented Oct 5, 2017 at 16:35
  • Also, the bash manual uses $((...)) not $[...] -- I think the latter syntax is deprecated, but I can't find that documented. Commented Oct 5, 2017 at 16:36
  • @glennjackman, edited, but I haven't encountered cases when $[...] syntax would not work ... yet Commented Oct 5, 2017 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. Commented Oct 5, 2017 at 16:43

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.