0

I have a numbered list of files like this:

01_file_name.log
01_file_name.txt
02_file_name.log
02_file_name.txt
03_file_name.log
03_file_name.txt
04_file_name.log
04_file_name.txt
05_file_name.log
05_file_name.txt

I want to insert new files for all 03* and 04* files. So from number 03 on wards all files need to be moved/renamed with its leading number increased by 2, so the list would look like this:

01_file_name.log
01_file_name.txt
02_file_name.log
02_file_name.txt
05_file_name.log
05_file_name.txt
06_file_name.log
06_file_name.txt
07_file_name.log
07_file_name.txt

I'm thinking of something like: bash script.sh 03 02 while 03 on $1 is the number of the file name to start with and 02 on $2 the number to be added. I tried to achieve this with a nested for-loop but I had to give up on the combination of using a counter ((++)) to make the loop run only through the files from the given number on wards.

nath
  • 5,694

4 Answers4

1

This does what you want. One thing: It only works in BASH, I think. MAXDIGITS is optional, I put it there in case you need to work with triple digits or more. If you don't use it, delete the corresponding lines.

    #!/bin/bash

    BOTTOM=$1
    SHIFT=$2
    MAXDIGITS=0$3
    for filename in $(ls -r *.{txt,log}); do
        PREFIX=${filename%%_*}
        NEWPREFIX=$(expr $PREFIX + $SHIFT)
        if [ $PREFIX -ge $BOTTOM ]; then
            NEWPREFIX=$(printf %${MAXDIGITS}d $NEWPREFIX)
            mv $filename ${NEWPREFIX}_${filename#*_}
        fi
    done

To use it:

    ./myscript <BOTTOM> <SHIFT> <MAXDIGITS>
    ### Example:
    ./miscript 03 02 2 #This means "Start at 03_filename.*, shift the prefixes by 2 and use 2 digits to construct the new prefixes".

If you have other files in the same directory that don't conform to the same syntax (prefix_filename.{txt,log}) you have to filter them. Also, I used the "_" to separate prefix from filename, so if you change that you'll need to fix the code.

1

For what it's worth, here is a bash one-liner that involves two process substitutions and a very simple awk procedure:

$ ls -r -1 *_file_name.{txt,log}    # list initial files in reverse order
05_file_name.log
05_file_name.txt
04_file_name.log
03_file_name.txt
03_file_name.log
03_file_name.txt
02_file_name.log
02_file_name.txt
01_file_name.log
01_file_name.txt

$ source <(awk 'BEGIN{FS="_"} $1>2 {prefix=$1+2; printf ("mv -f %s %0.2d_%s_%s\n",$0,prefix,$2,$3)}' <(ls -r -1 *_file_name.{log,txt}))

$ ls -1 *_file_name.{txt,log}     # list resulting files
01_file_name.log
01_file_name.txt
02_file_name.log
02_file_name.txt
05_file_name.log
05_file_name.txt
06_file_name.log
06_file_name.txt
07_file_name.log
07_file_name.txt

Explanation:

The one liner above is read, or at least "understood", from right to left.

  • ls -r -1 *_file_name.{log,txt} lists all files to be processed in reverse order, in one-column format.
    Note that if file prefixes (symbolized by *) include non-numeric characters, you might get unforeseen/unwanted results. To prevent that, filter files to be processed at this stage.

  • <(...) process substitution. It allows the previous result output to be referred to using a file descriptor, i.e. a special temporary file called a named pipe . For details issue man bash in terminal.

  • awk script:

    • BEGIN{FS="_"} before starting to process records, set the record's field separator to "_" (awk's 1st block)
    • awk's 2nd block's conditional, $1>2: only processes records whose filename's prefix is strictly greater than 2
    • prefix=$1+2 defines awk's "prefix" internal variable. Make it numerically equal to prefix $1 + 2
    • use printf("my_format",var1,var2,...) to print out formatted commands, separated by a new line. Note that formatting involves %0.2d, i.e. printing awk's variable "prefix" as a two-digit number with 0 padding whenever necessary. Output is:
      mv -f 05_file_name.log 07_file_name.log
      mv -f 05_file_name.txt 07_file_name.txt
      mv -f 04_file_name.log 06_file_name.log
      mv -f 04_file_name.txt 06_file_name.txt
      mv -f 03_file_name.log 05_file_name.log
      mv -f 03_file_name.txt 05_file_name.txt
  • treat that sorted output as yet another file (2nd process substitution) and source it to execute the mv -f ... commands.

If you wonder why repeatedly using inbound or outbound process substitution ( not a POSIX thing ! ) in bash is advantageous, see this for instance.

Final note:

Including runtime parameters in the above one-liner and encapsulating it in an executable shell script is easy, starting with defining VAR1 and VAR2 by assignment to the awk's procedure:

$ source <(awk -v VAR1=2 -v VAR2=2 'BEGIN{FS="_"} $1>VAR1 {prefix=$1+VAR2; printf ("mv -f %s %0.2d_%s_%s\n",$0,prefix,$2,$3)}' <(ls -r -1 *_file_name.{log,txt}))

And in an executable script, script.sh:

$ cat script.sh
#!/usr/bin/bash
if [ "$#" -eq 2 ] ; then
    source  <(awk -v VAR1=$1 -v VAR2=$2 'BEGIN{FS="_"} $1>VAR1 {prefix=$1+VAR2; printf ("mv -f %s %0.2d_%s_%s\n",$0,prefix,$2,$3)}' 
              <(ls -r -1 *_file_name.{log,txt})
             )
    exit 0
else
    echo "   Usage: script.sh VAR1 VAR2.\n    Abort execution (exit code 1)."
    exit 1
fi

where VAR1 and VAR2 have the meaning defined in OP. If you need to use that script in production environment, I'd strongly recommend adding a number of checks and fail-safes to ensure that your input files and variables' values are the expected ones at all times.

The script by @MarceloCastro is fine, even though it's based on a for loop, which I tend to avoid whenever possible. for loops tend to be slow, something you might notice if you process a large amount of files.

Cbhihe
  • 2,701
0

I have made this script that would fit your goal.

for files in *.extensions
do
  num=$(echo "$files" | awk '{print $1}' | cut -c1-2)
  if [ $num -gt 2 ]; then
     AS=$((num+2))
     temp=$(echo "$files" | cut -c 3-)
     mv $files 0$AS"$temp"
  fi
done
0

With zsh:

$ autoload zmv
$ zmv -n -f '(<3->)(_*)(#qnOn)' '${(l:2::0:)$(($1+2))}$2'
mv -- 05_file_name.txt 07_file_name.txt
mv -- 05_file_name.log 07_file_name.log
mv -- 04_file_name.txt 06_file_name.txt
mv -- 04_file_name.log 06_file_name.log
mv -- 03_file_name.txt 05_file_name.txt
mv -- 03_file_name.log 05_file_name.log

Then remove -n (for dry-run) to actually do it.

  • zmv pattern replacement: zmv is an autoloadable function that leverages the powerful zsh glob and expansion operators to do complex file renaming.
  • <x-y> matches decimal number x to y.
  • (#q...) glob qualifer
  • nOn: numerically order (reversed with capital O) by name. So 05... will come before 04... as needed here since we're incrementing the numbers.
  • ${(l:n::padding:)expansion}: left pad the expansion to length n using padding. So with ${(l:2::0:)}, left pad to length 2 with 0s.
  • $(($1+2)), the first captured group incremented by 2.
  • $2: the second capture group: what's after the leading number.