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.