Don't use a loop here, just use rename
:
$ ls
'a bad name.png' asdlha.gif askd.png 'a worse'$'\n''name.jpg' kasdjh.jpg
$ rename -n 's/.*(\..+)/"pic-" . ++$a . "$1"/se' *
a bad name.png -> pic0.png
asdlha.gif -> pic1.gif
askd.png -> pic2.png
a worse
name.jpg -> pic3.jpg
kasdjh.jpg -> pic4.jpg
If that looks right, re-run without the -n
to actually rename.
Explanation:
The rename
command is a perl script that can run perl code to rename files. Here, we are using the substitution operator (s/old/new/
) with a couple of special options:
- The
/s
makes the substitution operator treat its input as a single line, allowing .
to match newline characters. We need this to handle file names with newlines, as I have in the example above.
- The
/e
lets us evaluate the right hand side (s/left/right/
) as a perl expression. The expression "pic-" . ++$a . "$1"
means "the string pic-
, then the value of the variable $a
incremented by one, and then whatever was matched inside the parentheses on the left hand side". In our case, the extension.
Next, the regular expression used (.*(\..+)
) means "zero or more characters (.*
) until the last .
(\.
matches a literal .
), then one or more characters (.+
). Because the last \..+
is in parentheses, that makes what was matched (the extension) available as $1
on the right hand side.
After running the command above without the -n
, I was left with:
$ rename 's/.*(\..+)/"pic-" . ++$a . "$1"/se' *
terdon@oregano foo $ ls
pic-1.png pic-2.gif pic-3.png pic-4.jpg pic-5.jpg
Alternatively, you can do this in the shell with something like:
i=1; for file in *; do
ext="${file##*.}";
mv -- "$file" pic-$((i++))."$ext";
done
Here, we loop over all files (and directories, if you don't want to rename directories be careful), saving each as $file
. Then, we get the extension with ${file##*.}
. The ${variable##pattern}
format will remove the longest match for pattern
from the variable $variable
. By using ${file##*.}
we remove everything until the last .
, leaving only the extension which we save as $ext
.
Next, the mv
command will rename $file
to pic-
, plus the value of the variable $i
, which we initialize with i=1
, incremented by one, followed by a .
and the extension.
Like the rename
solution above, this too can handle arbitrary file names:
$ ls
'a bad name.png' asdlha.gif askd.png 'a worse'$'\n''name.jpg' kasdjh.jpg
$ i=1; for file in *; do
ext="${file##*.}";
mv -- "$file" pic-$((i++))."$ext";
done
$ ls
pic-1.png pic-2.gif pic-3.png pic-4.jpg pic-5.jpg
"${f%.*}"
and"${f##*.}"
but it's hard to tell from your descriptionls
this way is rarely a good idea."${f%.*}"
and"${f##*.}"
but I couldn't figure out how to get it to work in the above one-liner. I am mainly wondering if there's a global variable in bash to define file extensions%.*
in${f%.*}
matches the shortest "dot extension" - so in that sense, it's "global"