24

I have a directory which contains numbered image files, something like this:

01.png
02.png
03.png
03.svg
04.png
05.png
06.jpg
07.png
08.png
09.png
09.svg
10.png

Sometimes there may be multiple versions of a file in different formats (eg. a png and svg version of the 03 and 09 files above) but the numbers are otherwise consecutive. Typically there are 40-80 such files in each directory. The numbers correspond to the order these images appear in a manuscript (a Word document, but that's not important). There is no other way to determine the order of the images.

If I add a new image to the manuscript I need to place a copy of the image in this directory with the correct numbering. So if the new image is the fifth in the manuscript I need to rename the files in the directory to this in order to make room for it:

01.png
02.png
03.png
03.svg
04.png
06.png
07.jpg
08.png
09.png
10.png
10.svg
11.png

What is the most straightforward way from the command line, or from a script or macro to renumber all the files starting at a certain number? I have a standard Fedora Linux install using bash.

robertc
  • 343
  • 1
  • 2
  • 7

8 Answers8

19

I think that it should do the work:

#!/bin/bash

NEWFILE=$1

for file in `ls|sort -g -r`
do
    filename=$(basename "$file")
    extension=${filename##*.}
    filename=${filename%.*}

    if [ $filename -ge $NEWFILE ]
    then
        mv "$file" "$(($filename + 1))".$extension
    fi
done

Script takes one parameter - number of you new image.

PS. Put script in another directory than your images. In images directory there should be only images named in this way that you described.

pbm
  • 25,387
  • This looks promising, I'll try it in a few hours when I'm back on my laptop. – robertc Jun 11 '12 at 16:33
  • This will only works if your filename is not prefixed by non-numerics chars – mems Nov 15 '16 at 18:35
  • @mems The OP clearly states that filenames begin with a number – xhienne Jan 08 '17 at 13:16
  • 1
    (1) Note that, since the numbers in the input are all integers, you don’t need the inefficient -g option to sort; -n will suffice.  And, since the values all have the same number of digits, the default sort order will work.  In fact, the default *ls* order will work; i.e., ls -r.  (2) You might want to change \…`` to $(…) — see this, this, and this.  (3) Parsing the output of ls is generally discouraged. … (Cont’d) – G-Man Says 'Reinstate Monica' Jun 23 '20 at 00:33
  • (Cont’d) …  Although, as long as the directory contains nothing but image files with standard extensions (jpg, png and svg) whose names are all numbers, it probably won’t fail.  (And getting a list of filenames in reverse order can be somewhat tricky.)  (4) If the OP has 288 files (01.jpg through 96.svg) and wants to insert a new 94.png, he needs only to rename 96 → 97, 95 → 96 and 94 → 95.  But your code will loop through all the files.  Consider adding a break when you reach the last rename.  … (Cont’d) – G-Man Says 'Reinstate Monica' Jun 23 '20 at 00:33
  • (Cont’d) …  (5) This will fail if NEWFILE is less than ten, because, in bash arithmetic, numbers with a leading zero are considered to be octal, so 08 and 09 are not valid numbers.  (6) And, even if it weren’t for that problem, your script would still fail because it would rename 07.png → 8.png, 06.jpg → 7.jpg and 05.png → 6.png (i.e., it would not retain the leading zeros).  (7) You’re doing a halfway good job of quoting the things that need to be quoted.  … (Cont’d) – G-Man Says 'Reinstate Monica' Jun 23 '20 at 00:33
  • (Cont’d) … On the one hand, if the directory contains nothing but image files with names as specified, and IFS and NEWFILE contain reasonable values, you could probably remove all the quotes and not have it blow up in your face.  (It will blow up in the next guy’s face, six to eight minutes after you have left the building.)  But you should give a good example and quote "$NEWFILE", "$filename" and "$extension". – G-Man Says 'Reinstate Monica' Jun 23 '20 at 00:34
  • @xhienne: I don’t see where the OP “clearly states” anything about the filenames.  Yes, he shows example image file names that are nothing but a number and an extension, and so it’s probably reasonable to propose answers that rely on that assumption.  But you might as well say that he “clearly states” that there will be only twelve files, when actually he explicitly states that there will be 40-80 such files in each directory.  In fact, he never even says that the directory that contains the images contains only image files; i.e., that the .docx file won’t be in the same directory. – G-Man Says 'Reinstate Monica' Jun 23 '20 at 00:35
  • @G-ManSays'ReinstateMonica': First sentence: "I have a directory which contains numbered image files, something like this". I don't take the example for granted (i.e. I don't assume numbers are 2 digit integers). This is very clear to me that image files are named (number).(extension). If you object, no need to nitpick here, the best is to ask robertc, the original poster. – xhienne Jun 23 '20 at 16:31
5

This would be easier in zsh, where you can use

  • the On glob qualifier to sort matches in decreasing order (and n to use numerical order, in case the file names don't all have leading zeroes to the same width);
  • the (l:WIDTH::FILLER:) parameter expansion flag to pad all numbers to the same width (the width of the larger number).
break=$1   # the position at which you want to insert a file
setopt extended_glob
width=
for x in [0-9]*(nOn); do
  n=${x%%[^0-9]*}
  if ((n < break)); then break; fi
  ((++n))
  [[ -n $width ]] || width=${#n}
  mv $x ${(l:$width::0:)n}${x##${x%%[^0-9]*}}
done

In bash, here's a script that assumes files are padded to a fixed width (otherwise, the script won't rename the right files) and pads to a fixed width (which must be specified).

break=$1      # the position at which you want to insert a file
width=9999    # the number of digits to pad numbers to
files=([0-9]*)
for ((i=#((${#files}-1)); i>=0; --i)); do
  n=${x%%[^0-9]*}
  x=${files[$i]}
  if ((n < break)); then continue; fi
  n=$((n + 1 + width + 1)); n=${n#1}
  mv -- "${files[$i]}" "$n${x##${x%%[^0-9]*}}"
done
3

There doesn't seem to be much recent interest in this question but, should someone stumble upon it, there are three issues here.

  • One is how to select files to rename based on semantic criteria (range is not lexical and cannot be specified by wildcards or even regular expressions-- automata theory says that this is more complex than an NFA).

  • The second is how to change a name by modifying a portion of it.

  • The third is how to avoid name collision.

A script in Bash and many other languages can do this specific transform, but most of us would rather not have to write a program every time we want to rename a bunch of files.

With my (free and open source) rene.py you can do what you want but it takes two invocations to avoid the name collision problem.

  1. First
    rene ?.*/#7-80  %?.* B
    
    increments all names in the range, adding a prefix of % to avoid existing names.
  2. Then
    rene %* *
    
    removes this prefix from those files that have it.

I describe this at https://sourceforge.net/p/rene-file-renamer/discussion/examples/thread/f0fe8aa63c/

AdminBee
  • 22,803
  • Ranges of decimal integers can be specified with zsh's <x-y> wildcard. – Stéphane Chazelas Jun 19 '20 at 11:02
  • ?, and * are wildcard operators in most shells, # also in zsh -o extendedglob. # is special in most shells, and in some even if not delimited so if those rene ... are meant to be shell command lines, those arguments should probably be quoted (rene '?.*/#7-80' '%?.*' B...) – Stéphane Chazelas Jun 19 '20 at 11:58
  • Thanks for pointing that out. Those examples work in a Windows Cmd shell, which doesn't generally glob, but they do need quoting in Linux shells. – David McCracken Jun 22 '20 at 19:55
  • I don’t understand what you’re saying about specifying numbers in a range. Using your example of 7-80, we can specify that (1) in many shells, with {0{7,8,9},{1,2,3,4,5,6,7}{0,1,2,3,4,5,6,7,8,9},80}, (2) in bash (and maybe some others) with {07..30}, or (3) in bash, with extglob set, with @(0[7-9]|[1-7][0-9]|80). … … … … … … … … … … … P.S. I would be amazed if your rene %* * command worked in a Unixy shell without quoting at least the second *. Probably the %*, too. – G-Man Says 'Reinstate Monica' Jun 23 '20 at 00:46
  • @G-Man, {a,b} (from csh), or {x..y} (from zsh) are not matching/glob operators they are brace expansion which expands to the corresponding list of strings. @(x|y) (from ksh), or zsh's (x|y) or zsh's <x-y> are pattern matching, but only the latter can easily match arbitrary ranges of decimal integers. – Stéphane Chazelas Jun 23 '20 at 05:42
  • @StéphaneChazelas: Thanks for the history refresher. I know the difference between brace expansion and glob/wildcard (pathname expansion). Are you saying that the difference is relevant to this discussion? Yes, I know that, if you use brace expansion, you might want to couple it with a -f test; so what? I don’t understand David’s statement that file selection is a significant issue, and the range ‘‘*cannot* be specified’’. – G-Man Says 'Reinstate Monica' Jun 23 '20 at 21:19
  • @Stéphane Chazelas and anyone interested in bypassing command quoting in Linux. In Linux I invoke rene in a way that turns off globbing. This has two parts, which are defined in my .bash_aliases file. alias rene='set -f;rene.py' turns off globbing and invokes rene. rene.py(){ command rene.py "$@";set +f;} redefines rene.py to execute and then restore globbing. – David McCracken Nov 10 '20 at 21:06
  • I suppose you mean the bash shell here. Note that bash predates Linux and has been ported to most OSes. And most shells have been ported to Linux. That approach would leave globbing disabled after rene ... | something. In the zsh shell (another shell that predates Linux and used by many on Unix-like systems), you'd rather do alias rene='noglob rene' which doesn't have this kind of problem. – Stéphane Chazelas Nov 11 '20 at 05:51
  • Thanks for that information. I didn't know that zsh could do that. It is much simpler than my bash solution, which does work without leaving globbing disabled, which is why I use it instead of simply disabling globbing. I may ask Chet Ramey for his opinion of zsh. – David McCracken Nov 13 '20 at 03:10
2

Another zsh approach using its zmv autoloadable function for batch renaming:

$ autoload -Uz zmv # best in ~/.zshrc
$ zmv -f -n '(<5->)(.*)(#qnOn)' '${(l[2][0])$(($1 + 1))}$2'
mv -- 10.png 11.png
mv -- 09.svg 10.svg
mv -- 09.png 10.png
mv -- 08.png 09.png
mv -- 07.png 08.png
mv -- 06.jpg 07.jpg
mv -- 05.png 06.png

Remove the -n (for dry-run) if happy.

The -f (to disable safety checks) is needed as otherwise zmv would complain that we're renaming 09.png to 10.png which already exists for instance. However here, it's fine as we're processing the files in reverse numeric order (with the n and On qualifiers), so 10.png will already have been renamed to 11.png by the time we rename 09.png to 10.png.

1

This exact issue is covered in this article. Note that you would have to modify it to support the SVG and PNG formats, by adding a second MV step.

Jodie C
  • 1,879
  • I don't think it is the exact issue, that's going renumber all the images every time. I just want to renumber the images from a particular point. – robertc Jun 11 '12 at 16:30
1

If you save the next script as renn.sh, after installing it (see help for how to install it), you should be able to use:

renn "@." "[(02)i+1]." "{i -ge 5}" '<dir_path>'

To find out how to use the script, you can call it with the --help flag.

# REName with Numbers - a .sh (dash; bash; zsh - compatible) rename script 
# that can do basic arithmetic operations (+, -, padding with 0's) on 
# numbers in file names

Disclaimer:

By using this program you are assuming full responsibility (and the

author of this program shall not be held liable) for any data loss

or damage that may result from the use or misuse of this program.

DetectShell () { eval $1=&quot;&quot;; if [ -n "$BASH_VERSION" ]; then eval $1=&quot;bash&quot;; elif [ -n "$ZSH_VERSION" ]; then eval $1=&quot;zsh&quot;; elif [ "$PS1" = '$ ' ]; then eval $1=&quot;dash&quot;; else eval $1=&quot;undetermined&quot;; fi }

GetCharsArray () { DestroyArray $2

eval str=\&quot;\$$1\&quot;
i1=0;
qm_mask=&quot;&quot;
if [ -z &quot;$str&quot; ]; then
    eval $2\_0=&quot;0&quot;;
else
    rem_str=&quot;$str&quot;
    while [ &quot;$i1&quot; -le 1000000000 ]; do
        i1=$((i1+1));
        rem_str=&quot;${rem_str#?}&quot;
        qm_mask=&quot;$qm_mask?&quot;
        if [ -z &quot;$rem_str&quot; ]; then
            break;
        fi
    done
    eval $2\_0=&quot;$i1&quot;
    i2=1;
    while [ &quot;$i2&quot; -le &quot;$i1&quot; ]; do
        qm_mask=&quot;${qm_mask#?}&quot;
        eval crt_char=\&quot;\$\{str\%$qm_mask\}\&quot;
        str=&quot;${str#?}&quot;
        eval $2\_$i2=&quot;\&quot;\$crt_char\&quot;&quot;
        i2=$((i2+1))
    done
fi

}

GetStrLen () { eval str=&quot;$$1&quot; str_len=0; qm_mask="" if [ -z "$str" ]; then eval $2="0"; else rem_str="$str" while [ "$str_len" -le 1000000000 ]; do str_len=$((str_len+1)); rem_str="${rem_str#?}" qm_mask="$qm_mask?" if [ -z "$rem_str" ]; then break; fi done eval $2="$str_len" fi }

DestroyArray () { eval array_len=$(($1_0))

j=0;
while [ &quot;$j&quot; -lt &quot;$array_len&quot; ]; do
    j=$((j+1))
    unset $1\_$j
done
unset $1\_0

}

ValidateNaturalNumber () { eval tested_number=&quot;$$1&quot; condition="$tested_number -ge 0" #test if natural number result="true" if [ -n "$tested_number" ]; then eval [ $condition ] 2>/dev/null || { result="false"; } fi eval $2=$result }

ProcedureGetMarginsAndNumber () {

eval input_f_n=\&quot;\$$1\&quot;

neg_number_detected=&quot;false&quot;

if [ -n &quot;$pylon1_mask1&quot; ]; then
    margin1=&quot;${input_f_n%%&quot;$pylon1_mask1&quot;*[0-9]*&quot;$pylon2_mask1&quot;*}&quot;
else
    if [ &quot;${input_f_n%%&quot;-&quot;[0-9]*&quot;$pylon2_mask1&quot;*}&quot; = &quot;$input_f_n&quot; ]; then #if a negative number is not match:
        margin1=&quot;${input_f_n%%[0-9]*&quot;$pylon2_mask1&quot;*}&quot;
    else #if a negative number is match:
        margin1=&quot;${input_f_n%%&quot;-&quot;[0-9]*&quot;$pylon2_mask1&quot;*}&quot;

        neg_number_detected=&quot;true&quot;
    fi
fi
if [ -n &quot;$pylon2_mask1&quot; ]; then
    margin2=&quot;${input_f_n##&quot;$margin1&quot;&quot;$pylon1_mask1&quot;*[0-9]*&quot;$pylon2_mask1&quot;}&quot;
else
    if [ &quot;$neg_number_detected&quot; = &quot;false&quot; ]; then #if a negative number is not match:
        margin2=&quot;${input_f_n##&quot;$margin1&quot;&quot;$pylon1_mask1&quot;*[0-9]}&quot;
    else #if a neg number is match:
        margin2=&quot;${input_f_n##&quot;$margin1&quot;&quot;$pylon1_mask1&quot;*&quot;-&quot;[0-9]}&quot;
    fi
fi

number_temp=&quot;${input_f_n#&quot;$margin1&quot;&quot;$pylon1_mask1&quot;}&quot;
number=&quot;${number_temp%&quot;$pylon2_mask1&quot;&quot;$margin2&quot;}&quot;

}

PrintInTitle () { printf "\033]0;%s\007" "$1" }

PrintJustInTitle () { PrintInTitle "$1">/dev/stdin }

CleanUp () {

#Restore CTRL-C, CTRL-Z:
trap - INT
trap - TSTP

DestroyArray params
DestroyArray rn_from
DestroyArray rn_to
DestroyArray numbers
DestroyArray margin1
DestroyArray margin2
DestroyArray marks_temp_fns
DestroyArray coll_chk_1_markings
DestroyArray marks_special_temp_fns

#Restore the initial directory:
cd &quot;$initial_dir&quot;

#Clear the title:
PrintJustInTitle &quot;&quot;

#Restore IFS:
IFS=&quot;$old_IFS&quot;

}

trap1 () { if [ "$started_rn" = "false" ]; then printf "Cleaning up. Please wait..." CleanUp SPACE=" " BS="\b" SPACE_str="" BS_str="" for i in $(seq 1 40); do SPACE_str="$SPACE_str""$SPACE"; done for i in $(seq 1 40); do BS_str="$BS_str""$BS"; done printf "$BS_str" printf "$SPACE_str" printf "$BS_str" printf "Aborted. Nothing done.\n" exit fi }

DisplayHelp () { printf "\n" printf "renn - REName with Numbers\n" printf "\n" printf " Description:\n" printf " - renames filenames in a directory that match a mask (<mask1>) inexactly:\n" printf " - e.g.: <mask1>=&quot;(@)&quot; matches &quot;(@)&quot;\n" printf " - a '@' wildcard represents a number in the matched file/folder names.\n" printf " - note that the first match is taken into consideration - so the strings surrounding the '@' mask character should uniquelly identify the matched number in each file/folder name\n" printf "\n" printf " Syntax:\n" printf " renn <mask1> <mask2> <directory_path> [<condition>] [flags]\n" printf " where:\n" printf " <mask1> is a string containing exactly one '@' wildcard.\n" printf " <mask2> is a string containing one &quot;[...]&quot; arithmetic expression.\n" printf " An arithmetic expression is of the form &quot;[expr(i)]&quot;, where:\n" printf " &quot;i&quot; is a variable containing the number that the &quot;@&quot; wildcard replaces for each file/folder name\n" printf " &quot;expr(i)&quot; represents an arithmetic expression containing any of: the variable &quot;i&quot; (without the $ in front of it), &quot;-&quot;, &quot;+&quot;, &quot;(&quot;, &quot;)&quot;, SPACE, TAB. Note that only addition and substraction are permitted - this is with of scope of not loosing information from file names\n" printf " To pad the current arithmetic expression evaluation (&quot;expr(i)&quot;) with N zeroes use the syntax: &quot;[(0N)expr(i)]&quot;\n" printf " Example of arithmetic expression: [i+1]\n" printf " <directory_path> represents the directory in which to try to match <mask1> and in which to perform the rename operation.\n" printf " <condition>:\n" printf " A { <condition> } has the syntax of an &quot;if [ <condition> ]; then&quot; statement, in which &quot;-o&quot; (logical OR) and &quot;-a&quot; (logical AND) can be used, except for the fact that the variable &quot;i&quot; is used without the character $ in front (i.e.: i and not $i). See above what &quot;i&quot; represents.\n" printf " Like with the &quot;if&quot; syntax, subconditions can be grouped with escaped paranthesis: (, )\n" printf " For example: { (i -ge 10 -a i -le 100) -o (i -ge 210 -a i -le 300) } - represents a valid condition\n" printf " [flags] can be:\n" printf " --help or -h\n" printf " Displays this help information\n" printf " --install or -i\n" printf " Creates an executable symbolic link, in the location (directory) of the script\n" printf " --file or -f\n" printf " Tries to match file names (default)\n" printf " --dir or -d\n" printf " Tries to match directory names\n" printf " --preview or -p\n" printf " Previews the rename operation instead of properly renaming\n" printf "\n" printf " Recommendations/Notes:\n" printf " To avoid unexpected behaviour, always quote parameters.\n" printf " In order for this program to work correctly: new files must not be added and files must not be renamed/deleted in &quot;<directory_path>&quot;, while the program is running!\n" printf " Also, the rename operation shall not be interrupted (there are two major steps: Analyze and Preview/Rename).\n" printf " For more complex file name opperations, this script can be used together with the &quot;mmv&quot; utility\n" printf " Please note that: if they contain the newline character in them:\n" printf " - file names are ignored\n" printf " - masks are not handled properly\n" printf " When temporary file name collision is detected, the &quot;▄&quot; character is used at the beginning of the file names for two step rename\n" printf "\n" }

MAIN START

#Store initial IFS: old_IFS="$IFS" IFS=" $(printf "\t") ";

started_rn="false" E="ERROR"

DetectShell crt_shell

#Trap CTRL-C, CTRL-Z: trap 'trap1' INT trap 'trap1' TSTP

err="false" ls -1 "/dev/null">/dev/null 2>/dev/null || { printf "\n$E: &quot;ls -1&quot; (list one file per line) is needed in order for this program to function correctly!\n\n" err="true" }

printf "test"|sort -n>/dev/null 2>/dev/null || { printf "\n$E: &quot;sort -n&quot; (numeric sort) is needed in order for this program to function correctly!\n\n" err="true" }

if [ "$err" = "true" ]; then CleanUp && exit 1 fi

#Get the program parameters into the array "params": count=0 for i; do count=$((count+1)) eval params_$count=&quot;$i&quot; done params_0=$((count))

if [ "$params_0" = "0" ]; then #if no parameters are provided: display help DisplayHelp CleanUp && exit 0 fi

#Create a flags array. A flag denotes special parameters: help_flag="0" install_flag="0" file_flag="0" directory_flag="0" preview_flag="0" i=1; j=0; while [ "$i" -le "$((params_0))" ]; do eval params_i=&quot;${params_$i}&quot; case "${params_i}" in "--help" | "-h" ) help_flag="1" ;; "--install" | "-i" ) install_flag="1" ;; "--file" | "-f" ) file_flag="1" ;; "--dir" | "-d" ) directory_flag="1" ;; "--preview" | "-p" ) preview_flag="1" ;;

* )
    j=$((j+1))
    eval selected_params_$j=\&quot;\$params_i\&quot;
;;
esac

i=$((i+1))

done

selected_params_0=$j #Rebuild params array: DestroyArray params for i in $(seq 1 $selected_params_0); do eval params_$i=&quot;${selected_params_$i}&quot; done params_0=$selected_params_0

if [ "$help_flag" = "1" ]; then DisplayHelp elif [ "$install_flag" = "1" ]; then

file_path=&quot;$0&quot;
file_name_plus_ext=&quot;${file_path##*/}&quot;
file_dir_path=&quot;${file_path%/*}&quot;
file_name=&quot;${file_name_plus_ext%.*}&quot;
link_name=&quot;$file_name&quot;
install_path=&quot;$file_dir_path&quot;

#Install script (doesn't require root):
{
    cd &quot;$install_path&quot;
} &amp;&amp; {
    ln -sfn &quot;$file_name_plus_ext&quot; &quot;$link_name&quot; #Create the link (or override/update it)
} &amp;&amp; {
    chmod u+x &quot;$link_name&quot; #Make the link 'executable' (+x) for the current user (u)&quot;
} &amp;&amp; {
    printf '%s\n' &quot;If not done already, please update the PATH variable (for the current user) by adding the next line to the end of \&quot;~/.bashrc\&quot; file or \&quot;~/.zshrc\&quot; (depending on the shell used) file:&quot;
    printf '\n%s\n\n' &quot;export PATH=\&quot;\$PATH:\&quot;'$install_path' #This is for updating the PATH variable&quot;
    printf &quot;and then close and reopen the terminal, in order for the new PATH to be loaded.\n&quot;
}||{
    printf '\n%s\n\n' &quot;Could not install to the location: \&quot;$install_path\&quot;!&quot;
    CleanUp &amp;&amp; exit 1
}

else #RUN PROGRAM

PrintJustInTitle &quot;A: Initialising, please wait...&quot;


if [ &quot;$directory_flag&quot; = &quot;1&quot; ]; then
    if [ ! &quot;$file_flag&quot; = &quot;1&quot; ]; then
        dir=&quot;-d&quot;; file=&quot;-d&quot;
    else
        dir=&quot;-d&quot;; file=&quot;-f&quot;
    fi
else
    dir=&quot;-f&quot;; file=&quot;-f&quot; #(default)
fi

err=&quot;false&quot;

#Detect paths, conditions and parameters that are not paths or conditions:

i=3; #count - jump over &lt;mask1&gt; and &lt;mask2&gt;
p=0; #number of parameters that are paths
c=0; #number of parameters that are conditions
n=0; #number of parameters that are not paths or conditions

extra_cond=&quot;\$i -eq \$i&quot; #this condition is used to test if &quot;$i&quot; is a number or not
one_cond=&quot;1 -eq 1&quot; #default value in case one_cond is not set further

while [ &quot;$i&quot; -le &quot;$params_0&quot; ]; do
    eval params_i=\&quot;\$params_$i\&quot;
    GetCharsArray params_i params_i_chars_array

    first_char=&quot;$params_i_chars_array_1&quot;
    eval last_char=\&quot;\$params_i_chars_array_$params_i_chars_array_0\&quot;

    if [ &quot;$first_char&quot; = &quot;{&quot; -a &quot;$last_char&quot; = &quot;}&quot; ]; then
        c=$((c+1))
        one_cond=&quot;${params_i#?}&quot; #? = Remove &quot;{&quot; prefix:
        one_cond=&quot;${one_cond%?}&quot; # %? = Remove &quot;}&quot; suffix:
        GetCharsArray one_cond one_cond_chars_array
        j=1
        new_one_cond=&quot;&quot; #new_one_cond should contain i replaced with $i
        while [ &quot;$j&quot; -le &quot;$one_cond_chars_array_0&quot; ]; do
            eval crt_char=\&quot;\$one_cond_chars_array_$j\&quot;
            if [ &quot;$crt_char&quot; = &quot;i&quot; ]; then
                new_one_cond=&quot;$new_one_cond&quot;&quot;\$i&quot;
            else
                new_one_cond=&quot;$new_one_cond&quot;&quot;$crt_char&quot;
            fi
            j=$((j+1))
        done
        one_cond=&quot;$new_one_cond&quot;
    elif [ \( &quot;$params_i&quot; = &quot;.&quot; -o &quot;$params_i_chars_array_1$params_i_chars_array_2&quot; = &quot;./&quot; -o &quot;$params_i_chars_array_1$params_i_chars_array_2$params_i_chars_array_3&quot; = &quot;../&quot; -o &quot;$params_i_chars_array_1&quot; = &quot;/&quot; \) ]; then #if a path is detected
        p=$((p+1))
        one_path=&quot;$params_i&quot;
    else
        n=$((n+1))
    fi
    i=$((i+1))
done

set +f #enable globbing

mask1=&quot;$params_1&quot;
mask2=&quot;$params_2&quot;
directory=&quot;$one_path&quot;

printf 'mask1=&quot;%s&quot;\n' &quot;$mask1&quot;
printf 'mask2=&quot;%s&quot;\n' &quot;$mask2&quot;
printf 'directory=&quot;%s&quot;\n' &quot;$directory&quot;
if [ ! &quot;$c&quot; = &quot;0&quot; ]; then
    printf '%s\n' &quot;Condition = $one_cond&quot;
fi
printf &quot;\n&quot;

err=false

if [ &quot;$mask1&quot; = &quot;&quot; ]; then
    printf &quot;$E: &lt;mask1&gt; must not be empty!\n&quot;
    err=true
fi

if [ &quot;$mask2&quot; = &quot;&quot; ]; then
    printf &quot;$E: &lt;mask2&gt; must not be empty!\n&quot;
    err=true
fi

#mask1 must contain a &quot;@&quot; character = number wildcard:
first_rem_part=&quot;${mask1#*&quot;@&quot;}&quot;
if [ ! &quot;$first_rem_part&quot; = &quot;${mask1}&quot; ]; then #mask1 contains @
    second_rem_part=&quot;${first_rem_part#*&quot;@&quot;}&quot;
    if [ ! &quot;$second_rem_part&quot; = &quot;${first_rem_part}&quot; ]; then #mask1 contains two @
        printf &quot;$E: &lt;mask1&gt; must contain exactly one @ wildcard!\n&quot;
        err=true
    fi
else #mask1 does not contain @
    printf &quot;$E: &lt;mask1&gt; must contain the @ wildcard!\n&quot;
    err=true
fi

if [ &quot;$directory&quot; = &quot;&quot; ]; then
    printf &quot;$E: &lt;directory_path&gt; must not be empty (must be \&quot;.\&quot; or start with \&quot;./\&quot; or \&quot;/\&quot;)!\n&quot;
    err=true
fi

if [ -n &quot;$mask1&quot; -a &quot;${mask1%%*&quot;/&quot;*}&quot; = &quot;&quot; ]; then
    printf &quot;$E: &lt;mask1&gt; must not contain directory sepparators ('/')!\n&quot;
    err=true
fi
if [ -n &quot;$mask2&quot; -a &quot;${mask2%%*&quot;/&quot;*}&quot; = &quot;&quot; ]; then
    printf &quot;$E: &lt;mask2&gt; must not contain directory sepparators ('/')!\n&quot;
    err=true
fi

if [ &quot;$n&quot; -gt &quot;0&quot; ]; then
    printf &quot;$E: Too many parameters: expected &lt;mask1&gt;, &lt;mask2&gt;, &lt;directory_path&gt; and eventually &lt;condition&gt;!\n&quot;
    err=&quot;true&quot;
fi

if [ &quot;$p&quot; -gt &quot;1&quot; ]; then
    printf &quot;\n$E: Too many &lt;directory_path&gt; parameters provided: expected one after &lt;mask1&gt; and &lt;mask2&gt;!\n&quot;
    err=&quot;true&quot;
fi

if [ &quot;$c&quot; -gt &quot;1&quot; ]; then
    printf &quot;\n$E: Too many &lt;condition&gt; parameters provided: expected one after &lt;mask1&gt; and &lt;mask2&gt;!\n&quot;
    err=&quot;true&quot;
fi

if [ &quot;$err&quot; = &quot;true&quot; ]; then
    printf &quot;\n&quot;
    CleanUp &amp;&amp; exit 1
fi

rn_from_0=0
#&lt;mask1&gt; contains exactly one @ wildcard

#Generate pylon1 and pylon2 for mask1:
pylon1_mask1=&quot;${mask1%%&quot;@&quot;*}&quot;
pylon2_mask1=&quot;${mask1#*&quot;@&quot;}&quot;

#Generate pylon1 and pylon2 for mask2:
pylon1_mask2=&quot;${mask2%%&quot;[&quot;*}&quot;
pylon2_mask2=&quot;${mask2##*&quot;]&quot;}&quot;

#Detect arithmetic expression:
found_arith_expr=&quot;true&quot;
arith_expr=&quot;$mask2&quot;
if [ &quot;${mask2#*&quot;[&quot;}&quot; = &quot;$mask2&quot; ]; then
    found_arith_expr=&quot;false&quot;
else
    arith_expr=&quot;${mask2#*&quot;[&quot;}&quot;
    if [ &quot;${arith_expr%&quot;]&quot;*}&quot; = &quot;$arith_expr&quot; ]; then
        found_arith_expr=&quot;false&quot;
    else
        arith_expr=&quot;${arith_expr%&quot;]&quot;*}&quot;
    fi
fi
if [ &quot;$found_arith_expr&quot; = &quot;false&quot; ]; then
    printf &quot;\n$E: Expected at least one non-empty arithmetic expression ([...]) in &lt;mask2&gt;!\n\n&quot;
    CleanUp &amp;&amp; exit 1
fi

#Validate arithmetic expression (characters permitted are +, -, SPACE, TAB, (, ) and i ):
GetCharsArray arith_expr arith_expr_chars_array
#Count special characters in arithmetic expression:
i=1;
count=0;
while [ &quot;$i&quot; -le &quot;$arith_expr_chars_array_0&quot; ]; do
    eval crt_char=\&quot;\$arith_expr_chars_array_$i\&quot;
    case &quot;$crt_char&quot; in
        [0-9] | &quot; &quot; | &quot;\t&quot; | &quot;+&quot; | &quot;-&quot; | &quot;(&quot; | &quot;)&quot; | &quot;i&quot; )
        count=$((count+1))
    ;;
    esac
    i=$((i+1))
done
if [ &quot;$arith_expr_chars_array_0&quot; -gt &quot;$count&quot; ]; then
    printf &quot;\n$E: Characters not permited in arithmetic expression (the characters permitted are: +, -, SPACE, TAB, (, ) and i)!\n\n&quot;
    CleanUp &amp;&amp; exit 1
fi

#Check if the arithmetic expression has padding activated:
#initially, assume no padding:
arith_expr_padding=&quot;false&quot;
arith_expr_wo_padding=&quot;${arith_expr#&quot;(0&quot;*&quot;)&quot;}&quot;
if [ -n &quot;$arith_expr&quot; ]; then
    if [ -n &quot;$arith_expr_wo_padding&quot; ]; then
        arith_expr_padding=&quot;${arith_expr%&quot;$arith_expr_wo_padding&quot;}&quot;
        arith_expr_padding_number=&quot;${arith_expr_padding#&quot;(0&quot;}&quot;
        arith_expr_padding_number=&quot;${arith_expr_padding_number%&quot;)&quot;}&quot;

        ValidateNaturalNumber arith_expr_padding_number result
        if [ &quot;$result&quot; = &quot;true&quot; ]; then
            #remove padding from the arithmetic expression:
            arith_expr=&quot;$arith_expr_wo_padding&quot;
        else
            printf &quot;\n$E: Invalid padding, in arithmetic expression!\n\n&quot;
            CleanUp &amp;&amp; exit 1
        fi
    else
        printf &quot;\n$E: Number expected after padding, in arithmetic expression!\n\n&quot;
        CleanUp &amp;&amp; exit 1
    fi
else
    printf &quot;\n$E: Arithmetic expression must not be empty!\n\n&quot;
    CleanUp &amp;&amp; exit 1
fi

initial_dir=&quot;$PWD&quot; #Store initial dir
cd &quot;$directory&quot; 1&gt;/dev/null 2&gt;&amp;1||{
    printf '\n%s\n\n' &quot;$E: Cannot access directory: \&quot;$directory\&quot;!&quot;
    CleanUp &amp;&amp; exit 1
}

#Proceed to find matching file names and store them in numerically sorted order:
#Check file names starting with &quot;▄&quot; (error):
j=0
k=0
current_IFS=&quot;$IFS&quot;;
IFS=&quot;

"; err="false" for f in $(ls -1|sort -n); do

    j=$((j+1))

    PrintJustInTitle &quot;A: Step 1 of 4 - checking file name ${j}...&quot;

    if [ ! &quot;$f&quot; = &quot;${f#&quot;▄&quot;}&quot; ]; then #first char is &quot;▄&quot;
        printf '%s\n' &quot;$E: The first character of \&quot;$f\&quot; is \&quot;▄\&quot; (probably remainings from a interrupted temporary rename)!&quot;
        err=&quot;true&quot;
    elif [ ! \( &quot;$err&quot; = &quot;true&quot; \) -a \( \( $file &quot;$f&quot; \) -o \( $dir &quot;$f&quot; \) \) ]; then
        case $f in
            *&quot;$pylon1_mask1&quot;*[0-9]*&quot;$pylon2_mask1&quot;* ) #$f is match for &lt;mask1&gt;

                ProcedureGetMarginsAndNumber f

                i=$number
                eval [ \\\( $extra_cond \\\) -a \\\( $one_cond \\\) ] 2&gt;/dev/null &amp;&amp; {
                    #If &lt;condition&gt; is met:

                    #input file name = margin1 pylon1_mask1 number pylon2_mask1 margin2

                    k=$((k+1))

                    eval rn_from_$k=\&quot;\$f\&quot;
                    eval numbers_$k=\&quot;\$number\&quot;
                    eval margin1_$k=\&quot;\$margin1\&quot;
                    eval margin2_$k=\&quot;\$margin2\&quot;
                }
            ;;
        esac
    fi
done
IFS=&quot;$current_IFS&quot;;
eval rn_from_0=$((k))

if [ &quot;$err&quot; = &quot;true&quot; ]; then
    printf &quot;\n&quot;
    CleanUp &amp;&amp; exit 1
fi

if [ &quot;$rn_from_0&quot; = &quot;0&quot; ]; then
    printf &quot;\nNo file names match the specified mask!\n\n&quot;
    CleanUp &amp;&amp; exit 0
fi

#Generate 'renamed to' file names:
arith_err_encountered=&quot;false&quot;
for ii in $(seq 1 $rn_from_0); do

    PrintJustInTitle &quot;A: Step 2 of 4: file name $ii of ${rn_from_0}...&quot;

    #output file name = margin1 pylon1_mask2 arith_expr(number) pylon2_mask2 margin2

    eval new_f_n=\&quot;\$margin1_$ii\$pylon1_mask2\&quot;
    eval i=\&quot;\$numbers_$ii\&quot;; #should be ok becauses numbers are validated at mask1 matching; i is used in arithmetic expressions

    #Remove Zero's from the beginning of &quot;i&quot;:
    sign=&quot;&quot;
    if [ ! &quot;${i#&quot;-&quot;}&quot; = &quot;$i&quot; ]; then #i has a '-' sign
        sign=&quot;-&quot;
    fi
    t=&quot;${i#&quot;$sign&quot;&quot;0&quot;*[1-9]}&quot;
    zeros=&quot;${i%&quot;$t&quot;}&quot;
    zeros=&quot;${zeros%?}&quot; #Remove last non-0 digit
    zeros=&quot;${zeros#&quot;-&quot;}&quot; #Remove &quot;-&quot; sign
    i=&quot;$sign&quot;&quot;${i#&quot;$sign&quot;&quot;$zeros&quot;}&quot;

    if [ &quot;$crt_shell&quot; = &quot;dash&quot; ]; then
        arith_expr_eval=$(($arith_expr)) || arith_err_encountered=&quot;true&quot; #halts on error; catch error output
    else
        eval arith_expr_eval=\$\(\($arith_expr\)\) 2&gt;/dev/null || arith_err_encountered=&quot;true&quot; #does not halt on error; don't catch error output
    fi

    if [ &quot;$arith_err_encountered&quot; = &quot;false&quot; ]; then
        if [ ! &quot;$arith_expr_padding&quot; = &quot;false&quot; ]; then
            arith_expr_eval_padded=&quot;$(eval printf \&quot;\%0$arith_expr_padding_number\d\&quot; \&quot;$arith_expr_eval\&quot;)&quot;
            unset arith_expr_eval #zsh bug?
            arith_expr_eval=&quot;$arith_expr_eval_padded&quot;
        fi
        new_f_n=&quot;$new_f_n$arith_expr_eval&quot;
    else break; fi

    eval new_f_n=\&quot;\$new_f_n\$pylon2_mask2\$margin2_$ii\&quot;
    eval rn_to_$ii=\&quot;\$new_f_n\&quot;
done
rn_to_0=$rn_from_0

if [ &quot;$arith_err_encountered&quot; = &quot;true&quot; ]; then
    printf &quot;Arithmetic errors were encoutered: nothing done!\n\n&quot;
    CleanUp &amp;&amp; exit 1
fi

#Check if the arithmetic expression is constant:
if [ ! &quot;$rn_from_0&quot; = &quot;0&quot; ]; then
    i=1
    number1=$(($arith_expr))
    i=2
    number2=$(($arith_expr))
    if [ &quot;$number1&quot; = &quot;$number2&quot; ]; then
        printf &quot;$E: Constant arithmetic expression not allowed (=$number1)!\n\n&quot;
        CleanUp &amp;&amp; exit 1
    fi
fi

coll_err=&quot;false&quot;

### Check for file name collision (1):
coll_chk_1_markings_0=$rn_to_0
PrintJustInTitle &quot;A: Step 3 of 4: file name 1 of ${rn_to_0}...&quot;
if [ &quot;$rn_to_0&quot; -ge &quot;2&quot; ]; then
    prev_rn_from_fn=&quot;$rn_from_1&quot;
    prev_rn_to_fn=&quot;$rn_to_1&quot;
    fn_coll_detected=&quot;false&quot;
    for ii in $(seq 2 $rn_to_0); do
        PrintJustInTitle &quot;A: Step 3 of 4: file name $ii of ${rn_to_0}...&quot;
        eval crt_rn_from_fn=\&quot;\$rn_from_$ii\&quot;
        eval crt_rn_to_fn=\&quot;\$rn_to_$ii\&quot;

        if [ &quot;$prev_rn_to_fn&quot; = &quot;$crt_rn_to_fn&quot; ]; then
            eval coll_chk_1_markings_$ii=&quot;1&quot;
            eval coll_chk_1_markings_$((ii-1))=&quot;1&quot;
            if [ &quot;$fn_coll_detected&quot; = &quot;false&quot; ]; then
                printf '%s' &quot;$E: File name collision: \&quot;$prev_rn_from_fn\&quot;; \&quot;$crt_rn_from_fn\&quot;&quot;
                fn_coll_detected=&quot;true&quot;
                coll_err=&quot;true&quot;
            else
                printf '%s' &quot;; \&quot;$crt_rn_from_fn\&quot;&quot;
            fi
        else
            if [ &quot;$fn_coll_detected&quot; = &quot;true&quot; ]; then
                printf '%s\n' &quot; -&gt; \&quot;$prev_rn_to_fn\&quot;&quot;
                fn_coll_detected=&quot;false&quot;
            fi
        fi

        prev_rn_from_fn=&quot;$crt_rn_from_fn&quot;
        prev_rn_to_fn=&quot;$crt_rn_to_fn&quot;
    done
    #Treat last file separately:
    if [ &quot;$fn_coll_detected&quot; = &quot;true&quot; ]; then
        eval coll_chk_1_markings_$ii=&quot;1&quot;
        printf '%s\n' &quot; -&gt; \&quot;$crt_rn_to_fn\&quot;&quot;
        fn_coll_detected=&quot;false&quot;
    fi
fi

### Check for temporary file name collision (2):
temp_coll_detected=&quot;false&quot;
for j in $(seq 1 $rn_to_0); do
    PrintJustInTitle &quot;A: Step 4 of 4: file name $j of ${rn_to_0}...&quot;
    eval crt_rn_to_fn=\&quot;\$rn_to_$j\&quot;
    eval crt_rn_from_fn=\&quot;\$rn_from_$j\&quot;
    eval crt_coll_chk_1_marking=\&quot;\$coll_chk_1_markings_$j\&quot;
    if [ ! &quot;$crt_coll_chk_1_marking&quot; = &quot;1&quot; ]; then
        if [ -e &quot;$crt_rn_to_fn&quot; ]; then
            if [ \( $file &quot;$crt_rn_to_fn&quot; \) -o \( $dir &quot;$crt_rn_to_fn&quot; \) ]; then
                case $crt_rn_to_fn in
                    *&quot;$pylon1_mask1&quot;*[0-9]*&quot;$pylon2_mask1&quot;* ) #file name is match for &lt;mask1&gt;

                        ProcedureGetMarginsAndNumber crt_rn_to_fn

                        i=$number
                        eval [ \\\( $extra_cond \\\) -a \\\( $one_cond \\\) ] 2&gt;/dev/null &amp;&amp; {
                            if [ ! &quot;$temp_coll_detected&quot; = &quot;true&quot; ]; then
                                temp_coll_detected=&quot;true&quot;
                            fi
                            eval marks_temp_fns_$j=\&quot;\1\&quot;
                        } || {
                            if [ ! &quot;$coll_err&quot; = &quot;true&quot; ]; then
                                coll_err=&quot;true&quot;
                            fi
                            eval marks_temp_fns_$j=\&quot;\0\&quot;
                            printf '%s\n' &quot;$E: file name collision: $crt_rn_from_fn -&gt; $crt_rn_to_fn&quot;
                        }
                    ;;
                esac
            else
                if [ ! &quot;$coll_err&quot; = &quot;true&quot; ]; then
                    coll_err=&quot;true&quot;
                fi
                printf '%s\n' &quot;$E: file name collision: $crt_rn_from_fn -&gt; $crt_rn_to_fn&quot;
            fi
        else
            eval marks_temp_fns_$j=\&quot;\0\&quot;
        fi
    fi
done

if [ &quot;$coll_err&quot; = &quot;true&quot; ]; then
    if [ &quot;$preview_flag&quot; = &quot;1&quot; ]; then
        printf &quot;\nPreview - File name collision errors were encoutered!\n\n&quot;
    else
        printf &quot;\nRename - File name collision errors were encoutered: nothing done!\n\n&quot;
    fi
    CleanUp &amp;&amp; exit 1
fi

#If no errors were encountered:

if [ &quot;$preview_flag&quot; = &quot;1&quot; ]; then

    printf &quot;Previewing rename:\n&quot;
    for i in $(seq 1 $rn_from_0); do

        PrintJustInTitle &quot;P: Step 1 of 1: file name $i of ${rn_from_0}...&quot;

        eval crt_rn_from_fn=\&quot;\$rn_from_$i\&quot;
        eval crt_rn_to_fn=\&quot;\$rn_to_$i\&quot;
        printf '%s\n' &quot;$crt_rn_from_fn -&gt; $crt_rn_to_fn&quot;
    done
else
    printf &quot;\n&quot;
    err_encoutered=&quot;false&quot;

    #Disable CTRL-C, CTRL-Z while the rename is in progress:
    trap '' INT
    trap '' TSTP

    printf &quot;Rename in progress.. Please do not interrupt...\n&quot;&gt;/dev/stdin

    if [ &quot;$temp_coll_detected&quot; = &quot;false&quot; ]; then
        for i in $(seq 1 $rn_from_0); do

            PrintJustInTitle &quot;R: Step 1 of 1: file name $i of ${rn_from_0}...&quot;

            eval crt_rn_from_fn=\&quot;\$rn_from_$i\&quot;
            eval crt_rn_to_fn=\&quot;\$rn_to_$i\&quot;
            {
                mv -n &quot;./$crt_rn_from_fn&quot; &quot;./$crt_rn_to_fn&quot;
            }&amp;&amp;{
                started_rn=&quot;true&quot;
            }||{
                printf '%s\n' &quot;$E: RENAMING \&quot;$crt_rn_from_fn\&quot; TO \&quot;$crt_rn_to_fn\&quot;!&quot;
                err_encoutered=&quot;true&quot;
            }
        done
    elif [ &quot;$temp_coll_detected&quot; = &quot;true&quot; ]; then

        for i in $(seq 1 $rn_from_0); do

            PrintJustInTitle &quot;R: Step 1 of 3: file name $i of ${rn_from_0}...&quot;

            eval crt_rn_from_fn=\&quot;\$rn_from_$i\&quot;
            eval crt_rn_to_fn=\&quot;\$rn_to_$i\&quot;
            eval crt_marks_temp_fn=\&quot;\$marks_temp_fns_$i\&quot;
            if [ &quot;$crt_marks_temp_fn&quot; = &quot;0&quot; ]; then

                {
                    mv -n &quot;./$crt_rn_from_fn&quot; &quot;./$crt_rn_to_fn&quot;
                }&amp;&amp;{
                    started_rn=&quot;true&quot;
                }||{
                    printf '%s\n' &quot;$E: RENAMING \&quot;$crt_rn_from_fn\&quot; TO \&quot;$crt_rn_to_fn\&quot;!&quot;
                    err_encoutered=&quot;true&quot;
                }
            fi
        done

        for i in $(seq 1 $rn_from_0); do

            PrintJustInTitle &quot;R: Step 2 of 3: file name $i of ${rn_from_0}...&quot;

            eval crt_rn_from_fn=\&quot;\$rn_from_$i\&quot;
            eval crt_rn_to_fn=\&quot;\$rn_to_$i\&quot;
            eval crt_marks_temp_fn=\&quot;\$marks_temp_fns_$i\&quot;
            if [ &quot;$crt_marks_temp_fn&quot; = &quot;1&quot; ]; then
                if [ ! &quot;$crt_rn_from_fn&quot; = &quot;$crt_rn_to_fn&quot; ]; then #skip rename to itself cases
                    {
                        mv -n &quot;./$crt_rn_from_fn&quot; &quot;./▄$crt_rn_to_fn&quot;
                    }&amp;&amp;{
                        started_rn=&quot;true&quot;
                    }||{
                        printf '%s\n' &quot;$E: RENAMING \&quot;$crt_rn_from_fn\&quot; TO TEMPORARY FILE NAME \&quot;▄$crt_rn_to_fn\&quot;!&quot;
                        err_encoutered=&quot;true&quot;
                    }
                fi
            fi
        done

        for i in $(seq 1 $rn_from_0); do

            PrintJustInTitle &quot;R: Step 3 of 3: file name $i of ${rn_from_0}...&quot;

            eval crt_rn_from_fn=\&quot;\$rn_from_$i\&quot;
            eval crt_rn_to_fn=\&quot;\$rn_to_$i\&quot;
            eval crt_marks_temp_fn=\&quot;\$marks_temp_fns_$i\&quot;
            if [ &quot;$crt_marks_temp_fn&quot; = &quot;1&quot; ]; then
                if [ ! &quot;$crt_rn_from_fn&quot; = &quot;$crt_rn_to_fn&quot; ]; then #skip rename to itself cases
                    #started_rn is &quot;true&quot;
                    {
                        mv -n &quot;./▄$crt_rn_to_fn&quot; &quot;./$crt_rn_to_fn&quot;
                    }||{
                        printf '%s\n' &quot;$E: RENAMING TEMPORARY FILE NAME \&quot;▄$crt_rn_to_fn\&quot; TO \&quot;$crt_rn_to_fn\&quot;!&quot;
                        err_encoutered=&quot;true&quot;
                    }
                fi
            fi
        done
    fi

    printf &quot;\n\nRename done.\n&quot;&gt;/dev/stdin
fi

PrintJustInTitle &quot;Done. Cleaning up...&quot;

CleanUp

fi

MAIN END

0

Easier:

touch file`ls file* | wc -l`.ext

You'll get:

$ ls file*
file0.ext  file1.ext  file2.ext  file3.ext  file4.ext  file5.ext  file6.ext
  • How would I add the leading zero for 1-9? Also remember there may be two files 03.png and 03.svg. – robertc Apr 05 '18 at 11:39
  • 2
    This is barely close to what the question asks for.  (0) When the OP provides sample data, it’s rude to make up your own sample data in your answer.  Worse, it’s not clear that you even looked at the OP’s data.  (1) With the OP’s data — files ranging from 01 to 10 (Note: two-digit numbers (with leading zeros) starting at 01 (not 0) ) and the requirement to insert a new image #5, he needs to rename the 05-10 files to 06-11.  But your code creates a file12.ext (because, as the OP points out, two of the numbers between  01 and 10 are represented twice),  … (Cont’d) – G-Man Says 'Reinstate Monica' Jun 23 '20 at 00:45
  • (Cont’d) …  which isn’t helpful.   (2) As the OP points out, if there are fewer than ten files, your code will not generate the required leading zero. – G-Man Says 'Reinstate Monica' Jun 23 '20 at 00:46
0

Using Perl's rename commandline and :

shopt -s nullglob
printf '%s\n' {10..05}.* | rename 's/(\d+)/sprintf "%02d", $1 +1/e'