tillywern has asked for the wisdom of the Perl Monks concerning the following question:

Ok so I have come to a position where I want to rip out some C code an replace it with Perl. Problem is that the C program is passed a C structure as a command line argument. As such I parse it with offsets and treat it as a binary object.

With Bash this is easy because you have access to the command line arguments in a single location($*). C isn't much more difficult. With Perl I can't seem to find a structure that is functionally equivalent. I can't join @ARGV because the os throws away repeated white space. I don't have control of the calling program to change it to quote the input either.

I could pull it from /proc or ps easy enough but that wouldn't be portable.

What esoteric part of perl have I overlooked?

Replies are listed 'Best First'.
Re: Perl equivelant to bash '$*'
by Joost (Canon) on Jul 02, 2008 at 23:39 UTC
Re: Perl equivelant to bash '$*'
by graff (Chancellor) on Jul 03, 2008 at 01:50 UTC
    I'm sorry, but I'm finding it hard to understand what you are saying -- in other words, what you are saying makes no sense to me.

    "$*" in a bash shell refers to the list of all (0 or more) white-space separated tokens that follow the name of the shell script on the command line that invokes the shell script. Things that are within quotes are treated as a single "token", and can include any sort of white-space (even LF), but all spaces and tabs between tokens are discarded once the tokenization is done, and the shell script gets the list of tokens as $* -- for example:

    $ cat test.sh for i in $* do echo ==$i== done $ source test.sh one two three ==one== ==two== ==three==
    That's the same behavior you get with @ARGV in perl scripts.

    A shell command line cannot pass a C structure, except when either (a) the C structure is properly quoted and escaped so that the shell sees it as a single token containing literal characters, or (b) the C structure happens to be a single string of "non-shell-magic" characters -- no internal spaces or other things that the shell would interpret in some special way (e.g. angle brackets, ampersand, semicolon, etc). Either way, $* and @ARGV should be equivalent.

    Elsewhere you said:

    Basically the calling program passes 128 bytes of data. It so happens that the fields in the structure are strings and padded with spaces but there are no delimiters other than byte offset from the beginning. So I know the data is there I just don't know how to get at it from within perl because I don't know how to treat the command line as a large blob of binary data.

    Basically the command line would look like this.

    program.pl <128 bytes of data>

    So, you are not able to change the calling program, but you want your "program.pl" to replace an existing C program that previously occupied the initial slot in that command line, and you believe that the C program is treating its command line arg(s) differently from how Perl treats @ARGV -- is that it? Well, maybe the C program is treating its command-line args differently... do you have the C source code to check on that? But I don't think the "(argc,argv)" things that the C program gets from the command line can be any different from what Perl gets in @ARGV

    I suspect we could be more help, but IMHO, the problem as presented so far has invalid assumptions and insufficient detail.

    (updated to fix syntax in middle paragraph)

      Thanks everyone for your comments and schooling me in *nix. Since the source example I was given was bash using "$*" as a reference to the data structure instead of $1, I assumed they really wanted me to interpret all arguments sent to the program as a single string for parsing.

      Many of you are correct in saying that the picture I gave was unclear and probably had more to do with bash and proper argument passing technique than either C or Perl. For this I am sorry.

      As it turns out, the contents can be binary in nature but I have been told that the entirety of the data structure will be passed as the first argument. I will be able to parse the C structure fields out of $ARGV[0].

      Thanks again all for helping give me the correct questions to ask to pin then down.
Re: Perl equivelant to bash '$*'
by ikegami (Patriarch) on Jul 03, 2008 at 01:08 UTC

    The structure should be in a single argument. Sounds to me that structure isn't properly escaped into a shell argument.

    In unix systems, processes are not given a command line, they are given a list of strings where each element corresponds to an argument. The parent process doesn't pass a command line. That would be useless anyway since each shell has it's own command line syntax, assuming the parent is even a shell.

Re: Perl equivelant to bash '$*'
by pc88mxer (Vicar) on Jul 02, 2008 at 23:43 UTC
    How are calling the perl script? I would avoid calling an intermediate shell. If you execve() it directly you can preserve the whitespace in your binary argument. You'll be responsible for forming the argv[] array, though.
      The calling program has configurable settings that allow you to specify what program to start. It then starts that program and passes this C structure to it on the command line.

      Basically the calling program passes 128 bytes of data. It so happens that the fields in the structure are strings and padded with spaces but there are no delimiters other than byte offset from the beginning. So I know the data is there I just don't know how to get at it from within perl because I don't know how to treat the command line as a large blob of binary data.

      Basically the command line would look like this.
      program.pl <128 bytes of data>

      I'm only on the receiving side so it is a bit difficult. As you point out I could could put an intermediate shell script that does nothing but quote the original input and call the perl program but that seems a bit lame. It isn't that I can't implement a workaround, it is that I love the elegance of perl and thought there might be something I missed. A special variable or something like that.
        Pasting together the arguments is not going to work. Using $* only inserts one space between arguments. Just try this test program:
        #!/bin/bash echo "$*"
        Invoking the script as: script-name a b  c          d yields: a b c d

        From what you are saying, your program should only have one argument. You have to figure out why a shell is getting invoked.