Insert Character on batch folder rename
The question itself isn't exact too, because the OP talks about:


files names like ABC 123456 (without extension), but in the example code using *.txt extension
add a dash between two blocks (e.g. where is the space) like ABC - 123456, but in the next taking about counting to 4 and count 3, e.g. not exacltly clear what the script should do for example if find ABCD 12 or AB 345
also, want keep the trailing space, what is strange - but OK ;)


The problem is dividable to 3 separate parts:


selecting the right files for the rename
preparing the new filename, replacing/adding characters into the old one
the "physical" rename


Ad "selecting the right files". Selecting the files, can be done by some external utilities, for example by the find command.
The main benefits of the find are:


it can search files recursively in the sub directories too
is possible more precisely select the right files, e.g. exclude directories, select files by time or size and so on. (check from the terminal man find.)


Ad "prepare the new name". This can be done by external programs,
like sed or awk or any program what can manipulate text in shell
scripts, or it can be done (in some simple cases such this) withing
the bash itself - and is possible to save one expensive command execution.
(for hunderts of thousand files it makes a difference).

The following:

${file/ /-}


substitutes (replaces) one space (the / / part) with the dash (/-). So it is possible to write

mv "$file" "${file/ /-}"


and save the sed execution.

So one of the alternative solutions could be, for example the following:

#!/bin/bash
while IFS= read -r -d $'\0' filename
do
    newfilename="${filename/ /-}"
    [[ "$filename" != "$newfilename" ]] &amp;&amp; echo mv -i "$filename" "$newfilename"
done &lt; &lt;(find . -maxdepth 1 -type f -iname "* *.jpg" -print0)


The abobe is for the DRY RUN - it only will show, what will be done, for the real execution remove the echo.

Decomposition:


selecting the right files for the rename: the find . -maxdepth 1 -type f -iname "* *.jpg" -print0 will find all


what are only in the current directory (-maxdepth 1)
and they're plain files (-type f)
and their name matches (anything)(space)(anything).jpg case insensitively - e.g. the name must contains a space
supply the filenames as null terminated, so their name can safely contain newline character too. (-print0)

the cycle while IFS= read -r -d $'\0' filename; do ... done &lt; &lt;( )


reads the output from the above find command
where the filenames are null terminated (-d $'\0')
ignore any escaped characters (-r) /advanced topic - not needed to explained here/
the IFS=  prevents trimming of leading and trailing whitespace from the filename (also, a bit advanced topic)

preparing the new filename newfilename="${filename/ /-}"


is done by bash internally (without executing an external command).
if want preserve the trailing space after the dash use ${filename/ /- }

the actual renaming is done by the mv command (see man mv from the terminal), where


the -i what will ask the user if here are already a file with the new filename (don't override it)

and the mv is executed only when the newfilename is different from the filename [[ "$filename" != "$newfilename" ]]


The above solution is mostly error-prone, but it still isn't very effective,
because for hundreds of thousand files will execute hundreds of
thousand times the mv command.
The solution can be: using some
utility, what can read the filenames and do the renaming without
executing the mv command N-times.
For example, the "big gun" of system admins the "perl", as:

find . -maxdepth 1 -type f -iname "*.jpg" -print0 |\
    perl -0nle '$n=$_; $n=~s/ /-/; rename $_,$n unless($_ eq $n || -e $n)'


what will rename all files what outputs the find in one execution - e.g. much faster as thousands of mv executions.

Tor testing (DRY RUN) use the following:

find . -maxdepth 1 -type f -iname "*.jpg" -print0 |\
    perl -0nle '$n=$_; $n=~s/ /-/; print qq{old:$_ new:$n\n} unless($_ eq $n || -e $n)' #print instead of the rename


Ps: the perl is powerful enough to handle everything itself, eg. the find command too, but it is more advanced topic...

Ps2: my English is much worse as my bash, so someone could kindly edit this post for adding/correcting things.. ;)