4

In this answer, the command

rename -n 'our $i; s/_.*/sprintf("_%03d.png", $i++)/e' *.png

is used to rename the files

abc_128390.png
abc_138493.png
abc_159084.png
...

to

abc_001.png
abc_002.png
abc_003.png
...

I need to do exacly the same except start the numbering, say, from 28

abc_028.png
abc_029.png
abc_030.png
...

But when I change it to

rename -n 'our $i = 28; s/_.*/sprintf("_%03d.png", $i++)/e' *.png

it returns

rename(abc_128390.png, abc_028.png)
rename(abc_138493.png, abc_028.png)
rename(abc_159084.png, abc_028.png)
...

All the files get the same name.

Why is the numbering not increasing this time? How can I modify the above command corectly to do what I need?

3 Answers 3

4

As you've already discovered, putting our $i = 28; in the main script doesn't work because it resets $i to 28 for every filename - rename executes every statement in the main script once for every filename.

You can set an initial value for a variable (or any other setup code that only needs to be run once when the script first executes) in a BEGIN block - e.g. BEGIN {our $i = 28};.

You still have to declare the variable as a package global variable with our in both the BEGIN block and the main script because rename runs perl code in strict mode. See perldoc -f our and perldoc strict.

$ rename -n 'BEGIN {our $i = 28};
             our $i;
             s/_.*/sprintf("_%03d.png", $i++)/e' *.png
rename(abc_128390.png, abc_028.png)
rename(abc_138493.png, abc_029.png)
rename(abc_159084.png, abc_030.png)

Note: you do not have to declare the variable with our every time you use the variable. Declare it only once in each code block, i.e. once in BEGIN and once in the main script - either before it is first used in a code block, or when it is first used in a block. i.e. this works too:

$ rename -n 'BEGIN {our $i = 28};
             s/_.*/sprintf("_%03d.png", our $i++)/e' *.png

Also worth noting: if you need to use a variable that should be reset for every filename, declare it as a local variable with my rather than our. See perldoc -f my.

For a contrived example:

$ rename -n 'BEGIN {our $i = 28};
             our $i;
             my $formatted_number = sprintf("_%03d.png", $i++);
             s/_.*/$formatted_number/' *.png
rename(abc_128390.png, abc_028.png)
rename(abc_138493.png, abc_029.png)
rename(abc_159084.png, abc_030.png)

In this version, we don't need the /e modifier to the s/// operation because we don't need to evaluate the replacement as perl code, just use it as an interpolated variable. See perldoc -f s


One more thing:

Declaring $i as a state variable (see perldoc -f state) works too, without needing a BEGIN block but I would recommend avoiding it unless you have a really good grasp of variable scope in perl:

$ rename -n 'state $i = 28;
             s/_.*/sprintf("_%03d.png", $i++)/e' *.png
rename(abc_128390.png, abc_028.png)
rename(abc_138493.png, abc_029.png)
rename(abc_159084.png, abc_030.png)

See Persistent Private Variables and Coping with Scoping (which doesn't even mention state because it was written in 1998 before perl v5.10 when state was introduced, but is still good reading to understand perl variable scope)

15
  • Thanks for this thorough and helpful explanation! It worked! A lot is going on behind the scenes here which I was unaware of. Commented Oct 13 at 6:07
  • Your command is subject to potential shell injection Commented Oct 13 at 10:59
  • 1
    @GillesQuénot that would be difficult since the perl rename tool doesn't use the shell to rename files. If you're going to make a wild claim, provide some evidence to support it. If you're talking about *.png then a) it's not my command, it's the OP's command (and they copied it from the answer to another question), and b) it's easily solved by using ./*.png or -- *.png or piping a (preferably NUL-separated) list of filenames into rename, or any of the other common methods of dealing with unknown arbitrary filenames and c) it's beside the point for this particular Q. Commented Oct 13 at 13:39
  • Ah, I see you modified your comment since last time I read it. So why not just adding -- or ./ prefix if you know yourself that's like it's written, it's not 100% safe? Commented Oct 13 at 23:34
  • 1
    1. huh? i edited the comment almost immediately, there would barely have been time between my posting it and then editing it for you to read it. I can't see why that matters at all, anyway. 2. because that's not the point of the question, or my answer. The question is about how variables work in rename, not about filename args, which means it's fundamentally about perl variable scope which is much more interesting to me than adding yet another answer about filename args to the hundreds that already exist. 3. because I don't want to. it's my answer, not yours, and I don't follow your orders. Commented Oct 14 at 1:59
4

A simple approach here is to do:

our $i //= 28;

That is still declare it as a package global variable to work around strict but only assign 28 if not already defined.

Alternatively, with zsh's zmv:

autoload -Uz
n=28; zmv -n '(*_)<->(.png)' '$1${(l[3][0])$((n++))}'

(like for most implementations of rename, -n is for dry-run).

0

This is one way that use Perl main internals, without the need to use our global variable:

rename -n 'BEGIN{$main::i = 28} s/_.*/sprintf("_%03d.png", $main::i++)/e' ./*.png

The solution here is to use the fully-qualified scalar variable $main::i (same as when in package main).

This is something to have in the toolchest.

TIMTOWTDI

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.