8

man su says:

You can use the -- argument to separate su options from the arguments
supplied to the shell.

man bash says:

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

Well then, let's see:

[root ~] su - yuri -c 'echo "$*"' -- 1 2 3
2 3
[root ~] su - yuri -c 'echo "$*"' -- -- 1 2 3                                                       
2 3
[root ~] su - yuri -c 'echo "$*"' -- - 1 2 3                                                        
1 2 3
[root ~] su - yuri -c 'echo "$*"' - 1 2 3                                                           
1 2 3

What I expected (output of the second command differs):

[root ~] su - yuri -c 'echo "$*"' -- 1 2 3
2 3
[root ~] su - yuri -c 'echo "$*"' -- -- 1 2 3                                                       
1 2 3
[root ~] su - yuri -c 'echo "$*"' -- - 1 2 3                                                        
1 2 3
[root ~] su - yuri -c 'echo "$*"' - 1 2 3                                                           
1 2 3

Probably not much of an issue. But what's happening there? The second and the third variants seem like the way to go, but one of them doesn't work. The fourth one seems unreliable, - can be treated as su's option.

  • That's strange. I get exactly the results you expect (and I also agree with those expectations). I mean, in case of a second invocation, I do get "1 2 3" as an output. I'm using bash 4.2.45 on both source and destination accounts. – Krzysztof Adamski Apr 5 '14 at 21:37
9

What is happening is that the first argument you supply to the shell is the $0 parameter, (usually this would be the name of the shell). It is not included when you do echo $* since $* is every argument apart from $0.

Example:

# su - graeme -c 'echo "\$0 - $0"; echo "\$* - $*"' -- sh 1 2 3
$0 - sh
$* - 1 2 3

Update

Doing the following command:

strace -f su graeme -c 'echo $0; echo "$*"' -- -- 1 2 3

yields the strace line:

[pid  9609] execve("/bin/bash", ["bash", "-c", "echo $0; echo \"$*\"", "1", "2", "3"], [/* 27 vars */] <unfinished ...>

So somehow it seems that in this case su is gobbling up the extra -- without passing it to bash, possibly due to a bug (or at least undocumented behaviour). It won't however eat up any more than two of the -- arguments:

# su graeme -c 'echo $0; echo "$*"' -- -- -- 1 2 3
--
1 2 3
  • That I understand. I do: su - yuri -c 'echo "$*"' -- -- 1 2 3, the shell supposedly gets -- 1 2 3, but outputs only 2 3. Does it even make any sense? – x-yuri Apr 5 '14 at 21:18
  • And when I do bash -c 'echo $*' -- 1 2 3, it outputs 1 2 3 as expected. – x-yuri Apr 5 '14 at 21:19
  • @x-yuri, updated. This seems to be an su bug. – Graeme Apr 5 '14 at 21:41
5

Actually @Graeme's answer - and your question - are just referencing side-effects of how the shell handles "$@positional $*parameters". These are assigned by the shell to its arguments upon invocation and at any time later with the built-in set utility. They can be called upon at any time with either "$*" which splits each positional with the first character in "$IFS" or "$@" which quotes each positional and splits them with all of "$IFS."

man set

    NAME
       set — set or unset options and positional parameters

SYNOPSIS
       set [−abCefhmnuvx] [−o option] [argument...]

       set [+abCefhmnuvx] [+o option] [argument...]

       set −− [argument...]

       set −o

       set +o

If you already have the values that you're feeding the shell, you don't need to -- three times. Shell parameters are setable - always, at any time, not just at invocation (excepting $0 and -i):

su - mikeserv -c 'set -- "$*" ; echo "$*" ; 
    set -- 4 5 6 ; echo "$*"' -- -- 7 8 9

7 8 9
4 5 6

And all of that shell quoting can be confusing. This simplifies things a little:

( set -- 4 5 6
    su - mikeserv 4<<-\CMD /dev/fd/4 "$@"
    echo $0 "$*"
    set -- "$*"
    echo "$*"
    set -- 7 8 9
    echo "$*"
CMD
)

/dev/fd/4 4 5 6
4 5 6
7 8 9

The parent shell's arguments are set to 4, 5, and 6 and are then passed to the subshell invoked by su via the positional parameter "$@array".

Note how I ( subshell ) the above command - I do that because I don't want to mess around with my current shell environment - because I can inadvertently change something I'd rather not if I did with set.

ABOUT REDIRECTION:

First of all, your Unix system works with files - file permissions, file contents, file attributes. In one way or another, every data object you use can (and, at least in my opinion, should) be addressed as a file. Redirection points to a file - that's all. A <<HERE-DOCUMENT will describe a file in-line then redirect it. Either shell expansions are interpreted or they are not.

The asker notes in the comments below that when he attempts to use this method as the root user he is delivered a permissions error. When I responded I suggested he chown or chgrp the /dev/fd/${num} special file, but this is probably not the best method. The reason he encounters this issue is root is granted read permissions but not execute permissions. You can easily handle this by just avoiding an exec call. Instead of invoking the /dev/fd/${num} file directly on the command line do:

su -c '. /dev/fd/'${num} ${num}<<SCRIPT 

Using two heredocs can help with escaping. Here is what happens in every case:

NO SET IN <<HEREDOC

sh 3<<\CMD /dev/fd/3
    ( echo 'without set "$@" or \$@ in here-doc' ; echo
    set -- '1 "2" 3' 4 "5 6"
    su - mikeserv 4<<-UNQUOTED 5<<-\PREQUOTED /dev/fd/4
        echo UNQUOTED; echo $0 "$*"
        printf "%s\\t\\t%s\\t\\t%s\\t\\t%s\\n" $(printf "'%s' " "$@") \\
                $@ '$@' "$@" '"$@"' "'$@'" \$@ '\$@' "\$@" '"\$@"'
    . /dev/fd/5
    UNQUOTED
        echo PREQUOTED ; echo $0 "$*"
        printf "%s\t\t%s\t\t%s\t\t%s\n" $(printf "'%s' " "$@") \
                $@ '$@' "$@" '"$@"' \$@ '\$@' "\$@" '"\$@"'
    PREQUOTED
    )
CMD

OUTPUT

without set "$@" or \$@ in here-doc

UNQUOTED
/dev/fd/3 1 2 3 4 5 6
1 "2" 3         4               5 6             1
2               3               4               5
6               1 "2" 3 4 5 6           1 2 3 4 5 6             "1 "2" 3 4 5 6"
'1 2 3 4 5 6'           $@              "$@"
PREQUOTED
/dev/fd/5
''              $@              "$@"            $@
\$@             $@              "\$@"

SET "$@"IN <<HEREDOC

sh 3<<\CMD /dev/fd/3
    ( echo 'set "$@" and \$@ in here-doc' ; echo
    set -- '1 "2" 3' 4 "5 6"
    su - mikeserv 4<<-UNQUOTED 5<<-\PREQUOTED /dev/fd/4
        set -- "$@" "\$@"
        echo UNQUOTED; echo $0 "$*"
        printf "%s\\t\\t%s\\t\\t%s\\t\\t%s\\n" $(printf "'%s' " "$@") \\
                $@ '$@' "$@" '"$@"' "'$@'" \$@ '\$@' "\$@" '"\$@"'
        . /dev/fd/5
    UNQUOTED
        set -- "$@" "\$@"
        echo PREQUOTED ; echo $0 "$*"
        printf "%s\t\t%s\t\t%s\t\t%s\n" $(printf "'%s' " "$@") \
                $@ '$@' "$@" '"$@"' \$@ '\$@' "\$@" '"\$@"'
    PREQUOTED
)
CMD

OUTPUT

set "$@" and \$@ in here-doc

UNQUOTED
/dev/fd/3 1 2 3 4 5 6
1 "2" 3         4               5 6             1
2               3               4               5
6               1 "2" 3 4 5 6           1 2 3 4 5 6             "1 "2" 3 4 5 6"
'1 2 3 4 5 6'           1 2 3 4 5 6             $@              1 2 3 4 5 6
"$@"
PREQUOTED
/dev/fd/5 1 2 3 4 5 6 $@
'1              2               3               4
5               6'              '$@'            1 2 3 4 5 6
$@              $@              1 2 3 4 5 6             $@
"$@"            $@              \$@             $@
"\$@"  

SET "$@"AND MORE IN <<HEREDOC

sh 3<<\CMD /dev/fd/3
    ( echo 'set "$@" and \$@ AND additional parameters in here-doc' ; echo
    set -- '1 "2" 3' 4 "5 6"
    su - mikeserv 4<<-UNQUOTED 5<<-\PREQUOTED /dev/fd/4
        set -- "$@" "\$@" '7 "8" 9' 10 "11 12"
        echo UNQUOTED; echo $0 "$*"
        printf "%s\\t\\t%s\\t\\t%s\\t\\t%s\\n" $(printf "'%s' " "$@") \\
                $@ '$@' "$@" '"$@"' "'$@'" \$@ '\$@' "\$@" '"\$@"'
        . /dev/fd/5
    UNQUOTED
        set -- "$@" "\$@" '13 "14" 15' 16 "17 18"
        echo PREQUOTED ; echo $0 "$*"
        printf "%s\t\t%s\t\t%s\t\t%s\n" $(printf "'%s' " "$@") \
                $@ '$@' "$@" '"$@"' \$@ '\$@' "\$@" '"\$@"'
    PREQUOTED
    )
CMD

OUTPUT

set "$@" and \$@ AND additional parameters in here-doc

UNQUOTED
/dev/fd/3 1 2 3 4 5 6
1 "2" 3         4               5 6             1
2               3               4               5
6               1 "2" 3 4 5 6           1 2 3 4 5 6             "1 "2" 3 4 5 6"
'1 2 3 4 5 6'           1 2 3 4 5 6             7 "8" 9         10
11 12           $@              1 2 3 4 5 6             7 "8" 9
10              11 12           "$@"
PREQUOTED
/dev/fd/5 1 2 3 4 5 6 7 "8" 9 10 11 12 $@ 13 "14" 15 16 17 18
'1              2               3               4
5               6'              '7              "8"
9'              '10'            '11             12'
'$@'            '13             "14"            15'
'16'            '17             18'             1 2 3 4 5 6
7 "8" 9         10              11 12           $@
13 "14" 15              16              17 18           $@
1 2 3 4 5 6             7 "8" 9         10              11 12
$@              13 "14" 15              16              17 18
"$@"            $@              \$@             $@
"\$@"  
  • The problem is your first script gives me "8 9\n4 5 6\n". I'm running debian 6, bash-4.1.5 and su. – x-yuri Apr 6 '14 at 19:20
  • @x-yuri - and the second, which avoids all of the quoting mess? – mikeserv Apr 6 '14 at 20:25
  • If run from root it says: -su: /dev/fd/4: Permission denied. Do you know what that means, by the way? Otherwise it outputs as you say, but it doesn't address the question. Question is about using -- and -. – x-yuri Apr 6 '14 at 20:37
  • @x-yuri I think that means you should chown /dev/fd/4 for the duration you'll need it, or just chgrp. I haven't got much time to test right now. But that is a little beside the point, as was the other you don't need to pass arguments on the tail end at all - just work your quoting. See it now? – mikeserv Apr 6 '14 at 20:55
  • If we omit the issue with su being unable to work with redirected stdin, passing arguments is still better then injecting them into the command. Because in the latter case you need to escape them. – x-yuri Apr 6 '14 at 21:13

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.