Post by Hongyi Zhao[snipped]
Thanks a lot for your deep-in analysis and wonderful solution.
You can use the -- argument to separate su
options from the arguments supplied to the shell.
This last sentence does not describe exactly what su actually
does. It's rather a hint for the user how to separate su options
From options that are to be supplied to the shell.
“--” does not tell su, that all following arguments are arguments
supplied to the shell, it rather tells su, that all following
arguments are non-option arguments for su even if they start with
a dash.
For example, should
su -m
start a root shell, preserving the environment, or should it start
a root shell, supplying the option “-m” to it?
It will do the former. To achieve the latter, one could do
su -- -m
A program that conforms to the POSIX Utility Syntax Guidelines
(<http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02>)
treats all arguments from left to right that start with a “-” as
options, until an argument that does not start with a “-” is
encountered. Then this argument and all following arguments are
considered to be operands (even if they start with a “-”).
The special argument “--” tells the utility to stop option
processing even if the following arguments start with a “-”.
Programs, that have been written using the GNU getopt() function
may behave differently:
They treat all arguments from left to right that start with a “-”
as options, as long as there is no “--” argument reached. All
remaining arguments are considered to be operands.
For example,
an “ls” utility conforming to the POSIX Utility Syntax Guidelines, when
called like
ls -log log
will output a long listing (without showing owner and group) of
the file name “log”, whereas
ls log -log
will output a short listing of the file names “log” and “-log”.
An “ls” utility, that has been linked with the GNU getopt()
function, may behave slightly differently:
Called like
ls -log log
it will output a long listing (without showing owner and group) of the
file name “log” as above,
but called like
ls log -log
it will output a long listing (without showing owner and group) of
the file name “log” as well! What's going on there? GNU getopt()
does not stop option processing when encountering an argument not
starting with a “-”.
So to get the intended behavior (the short listing of the two files),
one has to supply the “--” parameter, which tells GNU getopt() to
stop option processing, treating all following parameters as
operands:
ls -- log -log
As this invocation lets behave a GNU getopt() as well as a POSIX
conformant getopt() the same, I use it whenever I want a utility
to stop option processing.
Post by Hongyi ZhaoAccording the above description, does it means that we should use the
su 3<<EOF
That might work, if su has been linked with GNU getopt() but fail,
if not: A su conforming to the POSIX Utility Syntax Guidelines
will call a root shell like
sh -- -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su
(which would tell the shell to run the script file “-c” supplying
it the two arguments “exec 0<&3 3<&- && "$SHELL" ${1+"$@"}” and
“su”) rather than like
sh -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su
which is the intended behavior.
In contrast,
will work with a GNU getopt() su as well as with a su conforming to
the POSIX Utility Syntax Guidelines.
Post by Hongyi Zhao[2] If I use the `-m, -p, --preserve-environment' option, does
it means that the "$SHELL" will not be needed?
I'm not sure, whether I understand you correctly. My intension
was, that root's shell, as specified by the user database (may it
be bash, ksh or sh for example), should start itself again.
Therefore I tell it to start "$SHELL". According to the su manual
page, the variable SHELL is set by su as specified in the user
database.
But if the -m, -p, or --preserve-environment options are given to
su, then "$SHELL" will specify the invocator's shell, unless the
user to be changed to has got a restricted shell. And this
reusing of the invocators SHELL variable may be bad, calling for
trouble.
So, maybe, it's better to explicitly specify what shell to run,
e.g.:
su -- root -c 'exec 0<&3 3<&- && bash ${1+"$@"}' -su
If you ask me, I recommend to never use the -m, -p, or
--preserve-environment options, as there are environment variables
that call for trouble when simply copied from the invocators
environment, for example HOME, TMPDIR, LOGNAME, USER.
Post by Hongyi Zhao-, -l, --login
When - is used, it must be specified before any username. For
portability it is recommended to use it as last option, before
any username.
My su manual page states
“When - is used, it must be specified as the last su option. The
other forms (-l and --login) do not have this restriction.”,
but that's not the behavior of my su (i.e. the manual page is
wrong): The argument “-” is not an option. It's a non-option
argument consisting of a solely “-” (and I guess, it's the same
with your su, despite of the changed manual page).
The behavior of my su is as follows:
First, process all options.
Second, if the first non-option argument is “-”, let the shell to
be started as a login shell and expect in the following argument the
user name.
If the first non-option argument is not “-”, consider it to be the
user name.
That's the traditional way for su to process the “-” argument. If
my su considered “-” to be an option, I would file a bug report.
Post by Hongyi ZhaoWhat form should I used for your above code?
As I want to be compatible with su in different unices, I don't
use the linux's su specific options “-l” and “--login” but rather
use the “-” non-option argument, which is fairly standard:
su -- - root -c 'exec 0<&3 3<&- && bash ${1+"$@"}' \
-su 3<<EOF
The “-” is the first non-option argument, because “--” explicitly
terminates option processing.
Post by Hongyi Zhao[4] Why must use two `su' in your code for this type of job?
You are telling the right questions, I see.
In the command
su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su
The second su is not a command, it's a copy of the shells
invocation name put after the “-c” “command line” arguments.
There are two things to be explained, here:
If one invokes a POSIX conformant shell (as can be seen in the
SYNOPSIS at
<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html>):
sh -c [-abCefhimnuvx] [-o option]... [+abCefhimnuvx] [+o option]...
command_string [command_name [argument...]]
giving it a command line to execute by means of the option “-c”
and the command_string as an argument, one can supply additional
arguments (as one can do when invoking a shell running a shell
script).
But there is a difference between an invocation of a shell running
a shell script and a shell running a command line, for example:
Put this shell script
---CUT-HERE
printf 'argument 0: %s\n%s addtional arguments.\n' "$0" "$#"
test "${#}" -eq 0 || printf '%s\n' "$@"
---CUT-HERE
in a file, e.g. “"$HOME"/script.sh”. Then you can run it,
supplying for example the two arguments “Hello” and “world”:
sh -- "$HOME"./script.sh Hello world
If you want to do the same by means of a command line, you have to
supply an extra argument after the command line before the two
arguments “hello” “world”:
sh -c '
printf '\''argument 0: %s\n%s additional arguments.\n'\'' \
"$0" "$#"
test "${#}" -eq 0 || printf '\''%s\n'\'' "$@"
' sh Hello world
Note the additional “sh” argument following the command line.
I prefer as the additional argument to specify the same as is used
as the invocation name when invoking the shell (see the arg0
parameter in the execl() function
<http://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html>),
which in this example is “sh”, when calling a shell as above, as
some shells (I don't remember which shell at which unixoid
operating system, maybe HPUX or Solaris it was) used that
additional argument rather than arg0 in determining whether they
should run as a (non-interactive) login shell.
When calling a “sh” by means of “su”, a traditional “su” supplies
as an invocation name (i.e. the arg0 parameter in the execl()
function)
* “su” (rather than “sh”), when calling a non-login sh, and
* “-su” (rather than “-sh”), when calling a login sh.
The intension of using “su” rather than “sh” is to let the called
sh test its special parameter “$0” (for example in the startup
files) to behave differently when called by “su”.
Therefore I prefer to run that same command line when invoking via
su as a non-login shell as
su -- root -c '
printf '\''argument 0: %s\n%s additional arguments.\n'\'' \
"$0" "$#"
test "${#}" -eq 0 || printf '\''%s\n'\'' "$@"
' su Hello world
and when invoking via su as a login shell as
su -- - root -c '
printf '\''argument 0: %s\n%s additional arguments.\n'\'' \
"$0" "$#"
test "${#}" -eq 0 || printf '\''%s\n'\'' "$@"
' -su Hello world