0

There is a lot of documentation about this online, and the bash man page is really nice. Even so, I can't understand why this is not working. Obviously I'm missing something...

I have a directory with 100 files, named file010, file020 ... file1000. I want them renamed to new00..99 using mv (well, mvg actually, buth both have the same output). When I run any of the following, I get target 'new99' is not a directory (always the last).

What am I missing?

id@machine  ~/temp/test  ls
file010  file090   file160  file240  file320  file400  file480  file560  file640  file720  file800  file880  file960
file020  file100   file170  file250  file330  file410  file490  file570  file650  file730  file810  file890  file970
file030  file1000  file180  file260  file340  file420  file500  file580  file660  file740  file820  file900  file980
file040  file110   file190  file270  file350  file430  file510  file590  file670  file750  file830  file910  file990
file050  file120   file200  file280  file360  file440  file520  file600  file680  file760  file840  file920
file060  file130   file210  file290  file370  file450  file530  file610  file690  file770  file850  file930
file070  file140   file220  file300  file380  file460  file540  file620  file700  file780  file860  file940
file080  file150   file230  file310  file390  file470  file550  file630  file710  file790  file870  file950
 id@machine  ~/temp/test  mv {*,new{00..99}}
/usr/local/bin/mvg: target 'new99' is not a directory
 ✘ id@machine  ~/temp/test  mv {file{010..1000..10},new{00..99}}
/usr/local/bin/mvg: target 'new99' is not a directory
 ✘ id@machine  ~/temp/test  mv {file*,new{00..99}}
/usr/local/bin/mvg: target 'new99' is not a directory
6
  • The last variable mv has to be the directory you're moving to. You're listing the files; it thinks you're telling it new99 is the directory in which to place all the files, but it can't find that directory.
    – pbhj
    Mar 2 '21 at 19:33
  • please stop playing with mv command on your real files that you don't know how it works Mar 2 '21 at 19:34
  • @αғsнιη they are not my real files, I'm just trying to learn some bash
    – noname
    Mar 2 '21 at 19:44
  • @pbhj I'm not following... if I run mv file1000 new99 there's no complaint.
    – noname
    Mar 2 '21 at 19:45
  • @pbhj Never mind, I got what you're saying. I'm sending a bunch of parameters to mv, not just 2. Thanks!
    – noname
    Mar 2 '21 at 19:57
4

What are you missing? How mv works.

When you provide exactly 2 files, you are renaming the first filename to become the second filename.

With more than 2 files, the last argument is taken as a directory, and all previous arguments are files to be moved there.

Refer to the man page.

To rename your files, you need a loop:

i=0
for file in *; do
    mv -v "$file" "$(printf 'new%02d' $i)"
    ((i++))
done

A thought: what if you have more than 100 files? How to avoid hardcoding %02d?

files=(*)
num=$(( ${#files[@]} - 1 ))
size=${#num}
for idx in "${!files[@]}"; do
    mv -v "${files[idx]}" "$(printf 'new%0*d' $size $idx)"
done
1
  • I was trying to do this in a quick & dirty one liner. But I get what you mean, I am not sending just 2 arguments to mv (which I wrongly assumed). I am actually sending the entire new{00..99}... Thanks!
    – noname
    Mar 2 '21 at 19:53
3

If you want to rename 100 files with mv, you need to call mv 100 times to rename each individual file. There's no real way around that.

To iterate over your files in numerical order from file010 to file1000 you could use

for name in file{010..990..10} file1000
do
    # rest of code goes here
done

The {010..990..10} is a brace expansion that expands to the strings 010, 020, 030 etc. up to 990 in steps of 10. Each such string will be prefixed by the string file. We add file1000 at the end as its number doesn't quite fit the pattern (it's one digit too long).

To rename the files in the loop, we can keep a counter, counting from zero up. The new filename is then constructed as the prefix file followed by the zero-filled two digit integer given by current value of the counter.

Adding this to our loop above:

count=0
for name in file{010..990..10} file1000
do
    printf -v newname 'file%.2d' "$(( count++ ))"
    mv -- "$name" "$newname"
done

Alternatively, instead of the brace expansion, we could use an arithmetic for loop like so:

for (( i = 1; i <= 100; ++i )); do
    printf -v name    'file%.3d' "$(( i * 10 ))"
    printf -v newname 'file%.2d' "$(( i - 1 ))"

    mv -- "$name" "$newname"
done

Using the Perl rename utility instead of mv:

rename -n -v 's/file(\d+)/sprintf "file%.2d", ($1\/10 - 1)/e' file*

This applies a Perl s/// (substitution) expression to each filename matching file* in the current directory. The expression matches the number after the file substring and uses that to compute the new filename number (it's divided by 10 and decremented by one).

Remove -n to run the command "for real" (-n is a "dry run" option).

A somewhat cleaner variant that doesn't use /e to evaluate the replacement string and which only mentions file once:

rename -n -v '/(file)(\d+)/; $_ = sprintf "%s%.2d", $1, ($2/10 - 1)' file*
4
  • I was hoping I didn't need a for loop, but I understand what you mean. Thanks! Btw, regarding the file1000 not matching the pattern, that's correct in bash, but I was surprised it worked in zsh (like touch file{010..1000..10}).
    – noname
    Mar 2 '21 at 21:02
  • 1
    @IulianDita That's because the two shells compute the width of the numerical strings differently, and bash will construct strings as wide as the widest generated string (in this case). Brace expansions are not standard, and non-standard features are not portable in general. In the zsh shell, you would not use a loop at all but zmv, which is a bit like the rename utility found on some systems. In fact' I might just show how to do it with rename as well... hold on.
    – Kusalananda
    Mar 2 '21 at 21:25
  • 1
    @IulianDita See updated answer.
    – Kusalananda
    Mar 2 '21 at 21:33
  • Awesome stuff! Thanks a lot!
    – noname
    Mar 3 '21 at 1:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.