http://qs321.pair.com?node_id=11110045

richard.sharpe has asked for the wisdom of the Perl Monks concerning the following question:

Dear Wells of Perl Wisdom,

when using $0 in my scripts, variable is replaced with whole invocation of the script with command line options and other parameters, but:

as some of parameters or option arguments are quoted with single or double quotes on command-line, that quotes are not present in $0 (because shell removes them before providing ARGV to Perl). Do you have some best practise, how to overcome this issue, having $0 interpolated exactly as put in the shell, also with quotes?

Thank you

Richard

UPDATE: My original question was not complete, actually I was using not just $0, but this:

my $tool_name = basename($0); my $tool_invocation = $tool_name." ".join(" ",@ARGV);

Replies are listed 'Best First'.
Re: preserve quotes after $0 interpolation
by ikegami (Patriarch) on Dec 13, 2019 at 06:48 UTC

    when using $0 in my scripts, variable is replaced with whole invocation of the script with command line options and other parameters

    No it's not. It's just the program name invoked.

    $ cat a.pl #!/usr/bin/perl use feature qw( say ); say $0; $ pwd /home/ikegami $ perl a.pl foo a.pl $ perl ./a.pl foo ./a.pl $ perl /home/ikegami/a.pl foo /home/ikegami/a.pl $ perl "$HOME/a.pl" foo /home/ikegami/a.pl $ a.pl foo ./a.pl $ ./a.pl foo ./a.pl $ /home/ikegami/a.pl foo /home/ikegami/a.pl $ "$HOME/a.pl" foo /home/ikegami/a.pl

    If you wanted to recreate the command, you could use

    use String::ShellQuote qw( shell_quote ); my $cmd = shell_quote($0, @ARGV);
    or
    use String::ShellQuote qw( shell_quote ); my $cmd = shell_quote($^X, "--", $0, @ARGV);

    Note that neither of these solutions will recreate options passed to perl rather than the script (such as -p, -C, etc.) Perl doesn't provide this information to the script.

    Note that the command will be equivalent to the one used (subject to the aforementioned caveat), but it won't be a character for character match to what was used. For example, the exact quoting used may be different. There's no reliable way to get the command as entered because

    • there are multiple shells with different syntax, so there's no point in having the command as it was passed to the shell,

    • the command may have been part of larger command or used variables that are no longer accessible, so there's no point in having the command as it was passed to the shell, and

    • many programs aren't even launched from a shell!

      Note that neither of these solutions will recreate options passed to perl rather than the script (such as -p, -C, etc.) Perl doesn't provide this information to the script.

      But see Devel::PL_origargv.

      use String::ShellQuote qw( shell_quote );

      The last two times I looked at String::ShellQuote (Re^2: Passing values from Perl script to shell script in 2009 and Re^4: quoting/escaping file names in 2014), it did not look good. The last change is from 2010. So, String::ShellQuote still has the same problems as in 2009, quoting myself:

      It works just for some unnamed version of some unnamed bourne shell. The author wanted to add more shells, but he did not since 2005. The test.t look very strange, especially I don't see any reasonable test for passing arguments via a shell. So, it's old, unmaintained, not well-tested, and broken for all shells except for that unspecified bourne shell. Use the multi-argument forms of exec, system, or open instead, they do not need quoting.

      See also The problem of "the" default shell.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

        You seem to allege some problem with shell_quote, but none of the linked post identify one. The all seemed centered around the idea of avoiding the shell is better. While true, that's not a problem with shell_quote.

        What do you mean by "It did not look good". What problem does it have?

        The posts you link only rant the module about the fact the module only supports sh. Claiming that makes the module broken is ridiculous.

      I'm sorry, my original question was not complete (fixed), of course there should be also @ARGV in question and actually I was doing that this way:

      my $tool_name = basename($0); my $tool_invocation = $tool_name." ".join(" ",@ARGV);

      The reason, why I am interested in such thing, is, that I am generating some configurations, with comments containing also how was it generated, to be able reproduce that generation just from comment inside it, just by copy & paste whole command from comment to shell. Restriction to particular shell type, e.g. bash, is no problem for me in my use case, therefore the idea with shell history fits my needs maybe the best.

      Thank you for tips!

Re: preserve quotes after $0 interpolation
by roboticus (Chancellor) on Dec 13, 2019 at 03:49 UTC

    richard.sharpe:

    I'd be surprised if you can find a satisfactory way to do it. If you try NetWallah's suggestion of slogging through history, you may find that the user put a blank before the command and has history suppressing lines beginning with blanks. The user could be running in a number of different shells, so shell trickery could be similarly difficult. So knowing which quotes to use, and/or whether there's any path prefix is added would be quite a trick.

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

Re: preserve quotes after $0 interpolation
by bliako (Monsignor) on Dec 13, 2019 at 07:02 UTC

    quoting @ARGV items back could be a challenge only when they are quoted. Otherwise quote them with single quotes, since bash had already interpolated anything that should had been interpolated.

    In the case there are quotes, I have not given it too much thought, but this can quote back your command line to equivalent effect to the original (but not the same):

    print join ' ', ($0, map { qw/$'/ . s/'/\\'/gr . qw/'/ } @ARGV);

    Assuming bash. If you find you need a lot of tweaking and extra cases handling then the path you are following is the wrong one and you probably do not need the actual command line verbatim.

    btw, $0 holds your command and @ARGV your command arguments.

    bw, bliako

    p.s. at the time of writing this, ikegami suggested something more robust because it uses an existing module (with probably all the edges covered). What the heck! Every time a one-liner is thrown away a sailor is drowned in the high seas, so here you go.

      Thank you. I fixed my original question ($0 was not enough, of course). The idea with the shell history in combination with requirement to invoke script from particular shell and by particular way, would be probably the way for me. I would like to catch it as close as original as possible, therefore I am escaping ~ with \~ and * with \*, to let Perl, and not shell, expand tem, to preserve unexpanded version of invocation in Perl script.

        Here is a hint: file bashhist.c in the bash source code (v5), modify function pre_process_line() to dump line to your own file so that you do not rely on users' history settings (which can be disabled, purged or modified by other shells running at the same time). That's based on my diagonal look at the source code.

        If what you are doing is worth the investment, find out exactly where bash executes commands and intervene before expanding and interpolating the command line (that may be in readline). In this way you may even be able to pass an extra argument to Perl with the original commandline! far-fetched I know.

        bw, bliako

Re: preserve quotes after $0 interpolation
by NetWallah (Canon) on Dec 13, 2019 at 03:29 UTC
    You could use the shell (bash) 'history' command and grep for the most recent invocation of your script.

                    "From there to here, from here to there, funny things are everywhere." -- Dr. Seuss

Re: preserve quotes after $0 + @ARGV interpolation
by bliako (Monsignor) on Dec 13, 2019 at 14:52 UTC

    Another way: write your own command launcher. That can just be a basic unix shell, e.g. see here https://www.geeksforgeeks.org/making-linux-shell-c/ or modify bash as I suggest in one of my answers to you below.

    Here is a basic command launcher in Perl. WARNING: it is not secure to execute user input!

    use strict; use warnings; my ($fh, $cmd); my $cmdI=0; open($fh, '>', 'log.txt'); while(1){ print ++$cmdI."> "; $cmd = <STDIN> || exit; print $fh $cmd; chomp($cmd); # this way you pass your command via the default shell with all the +interpolations happening #system($cmd) && die "failed to run command: $cmd"; # this way you avoid the default shell and launch your perl script d +irectly but # the command line is just one huge parameter (it does not break on +whitespace, like a shell would do) $cmd=~ s/^mycommand\.pl// || die "$0 : your command must always be ' +mycommand.pl'\n"; system('mycommand.pl', $cmd); }
    #!perl # mycommand.pl print "got params (number of params: ".@ARGV.") : " . join(' ', @ARGV) +."\n";

    It will first log what you wrote without interpolation or checking if quotes balance, etc. Then it can pass what you write to the default shell for interpolating it, breaking it into separate command line arguments and then finally launching your command with these arguments. Or it can pass what you write to the shell your command (which it did launch without a shell - see the array-mode of system()) after it logs it with nothing interpolated. In this way you also get one huge command line parameter because the shell is not invoked in order to interpolate and break it on spaces. You see the shell does (irritating) interpolation but also breaks your parameters into an array to go to ARGV.

    bw, bliako

    EDIT: don't be scared by the "one huge command line parameter" I mentioned above, because Getopt::Long can parse it as a single string (as opposed to an ARGV-style array, which also does parse really beatifully).