perl -s switch documentation confusion

161 Views Asked by At

Examples on stackoverflow involving -s switch (e.g. https://stackoverflow.com/a/29613573/14557599) give position of -- like:

perl -s -e 'if ($xyz) { print "$xyz\n" }' -- -xyz

perldoc perlrun:

-s   enables rudimentary switch parsing for switches on the command line
     after the program name but before any filename arguments (or before
     an argument of --). Any switch found there is removed from @ARGV
     and sets the corresponding variable in the Perl program. The
     following program prints "1" if the program is invoked with a -xyz
     switch, and "abc" if it is invoked with -xyz=abc.

         #!/usr/bin/perl -s
         if ($xyz) { print "$xyz\n" }

I'm confused with "before an argument of --", because example above works, -xyz is after --. If written as in docs:

perl -xyz -- -s -e 'if ($xyz) { print "$xyz\n" }' 
Can't open perl script "-s": No such file or directory

Per my understanding how it works I want to write in doc:

after the program name (or after an argument of --) but before any filename arguments

Is my change correct? Maybe sometime before syntax of perlrun was indeed as described in docs but was changed later but docs were updated to reflect that?

Edit 2:

After reading all the answers:

after the program name (or after first occurrence of an argument of -- if the program is specified via -e or -E switches) but before any filename arguments (or before --, first occurrence in case of program name and second in case of the program specified via -e or -E switches)

TL;DR

Edit:

In response to am answer by zdin (note: I have not found definition of option, switch; seems those are synonyms and both are used as both bash command line arguments and in perl program on shebang line arguments).

echo a-a-a > 1.txt

q.pl

#!/usr/bin/perl -sp
s/a/b/;
if ($xyz) { print "$xyz\n" }

w.pl

#!/usr/bin/perl -s
s/a/b/;
if ($xyz) { print "$xyz\n" }

I can call *.pl passing 1.txt as argument w/out --:

./q.pl -xyz=a 1.txt 
a
b-a-a

perl -p ./q.pl -xyz=a 1.txt 
a
b-a-a

One-liner:

perl -spe 's/a/b/;if ($xyz) { print "$xyz\n" }' -- -xyz=a 1.txt
a
b-a-a

That shows my reformulation is valid for both running via program name and one-liners.

P.S.

perl --version

This is perl 5, version 34, subversion 0 (v5.34.0) built for x86_64-linux-gnu-thread-multi
3

There are 3 best solutions below

16
zdim On BEST ANSWER

The quoted statement in perldoc is correct, if incomplete as it doesn't mention a different behavior for a one-liner. The confusion stems from peculiarities of option parsing with -s in presence.

That particular statement refers to running a program-in-a-file, as shown. With

#!/usr/bin/perl -s

use warnings;
use strict;
use feature 'say';

our $tt;  # -s populates package variables

say $tt // "no -tt...";

we can do

perl test_s_switch.pl -tt=X -- arguments

to mark the end of options ("switches"), after which arguments come (for @ARGV). This is needed if the first of the following arguments happens to look like an option (starts with -); or we can leave out -- otherwise (if the first argument doesn't start with -), or if no arguments are being passed

perl test_s_switch.pl -tt=X

These print X. (Or perl -s test_s_switch.pl ... w/o the -s on the shebang line.)

But for a command-line program ("one-liner") it goes the way you observe

perl -s -wE'say $tt // "no -tt"' -- -tt=X

and we get X printed. Here the -- is necessary to indicate end of options so that after it go arguments. This distinction is stated at the beginning of Command Switches in perlrun

A -- signals the end of options and disables further option processing. Any arguments after the -- are treated as filenames and arguments.

The reason for this difference is that with the program given in a file the options ("switches") must come before the program-filename, what leaves the place for -tt=X (argument for -s) to come right after the program-filename, before -- or arguments. But with a command-line program ("one-liner") the program itself is a part of a switch (-e'...') so other options may follow, so to pass an argument for -s (-tt=X) we must have -- first, to indicate the end of options. Tricky, eh.

With a one-liner, trying without -- makes -tt=X into switches (t, t, and =X)

perl -s -wE'say $tt // "no -tt"' -tt=X   # um, no...

and results in

Unrecognized switch: -=X  (-h will show valid options).

since -t is a legitimate switch and repeating it (-tt) is accepted.


At this point I have to note a remark at the end of the -s switch description in perlrun

...
For these reasons, use of -s is discouraged. See Getopt::Long for much more flexible switch parsing.

2
Shawn On

First, perl processes the arguments to the perl interpreter, ending with the first -- or non-option argument (The filename unless you're using -e). Stuff after that point populates @ARGV. Then if present the -s argument causes those arguments to be processed for options, stopping at the first non-option or --. So you can have two instances of --, each signalling to stop processing a different set of options, one for perl itself, one for the program being run. Some examples:

# No --
$ perl -s -E 'say "@ARGV"; say $c' -c a b c
-e syntax OK
# -- to stop perl option processing
$ perl -s -E 'say "@ARGV"; say $c' -- -c a b c
a b c
1
# Two --'s, one for perl options, one for -s options
# Blank line because no $c is being set (Would generate a warning
# with -w/use warnings. Does set $xyz)
$ perl -s -E 'say "@ARGV"; say $c' -- -xyz -- -c a b c
-c a b c

# ex.pl does the same as the code for the above -E option. Its presence stops
# perl option processing
$ perl -s ex.pl -c a b c
a b c
1
# -- to stop -s option processing; sets $xyz
$ perl -s ex.pl -xyz -- -c a b c
-c a b c

# With a filename starting with - so it's treated as a filename not an option
$  perl -s -- -ex.pl -c a b c
a b c
1
# And two instances of --
perl -s -- -ex.pl -- -c a b c
-c a b c

6
ikegami On

It's referring to the first -- script argument, as opposed to the first -- perl argument.


Let's assumes file.pl starts with

#!/usr/bin/perl -s

Normally, one uses

file.pl -j             # $j = 1;

Now let's say you wanted -k to end up in @ARGV instead of being treated as an option. We can't use

file.pl -k             # $k = 1;  XXX

So we add a --.

file.pl -- -k          # @ARGV = '-k';

Combined:

file.pl -j -- -k       # $j = 1; @ARGV = '-k';

Now, what if you used perl -se'...' instead? Until the first argument that doesn't start with -, arguments are passed to perl, not -s or the script. So you have to mark the end of arguments instead.

perl -se'...' -i -- -j -- -k
  • -i is processed by perl, activating in-place editing.
  • -j is processed by -s, resulting in $j = 1.
  • -k is placed in @ARGV.