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" ]] && echo mv -i "$filename" "$newfilename"
done < <(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 < <( )
- 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.. ;)
Best Answer
You can do the following in Terminal:
This will recursively rename all .txt files in the current directory to .md.