Discussion:
su: must be run from a terminal
(too old to reply)
Hongyi Zhao
2017-03-13 13:58:08 UTC
Permalink
Hi all,

I try to use su to execute some commands with heredoc in my script like
follows:

su root <<EOF
bla
bla
...
EOF

But, when I input the password of root, I meet the following error:

su: must be run from a terminal

And as a result, all of the commands within the heredoc not executed at
all.

How to solve this issue?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
hymie!
2017-03-13 14:13:11 UTC
Permalink
In our last episode, the evil Dr. Lacto had captured our hero,
Post by Hongyi Zhao
Hi all,
I try to use su to execute some commands with heredoc in my script like
And as a result, all of the commands within the heredoc not executed at
all.
How to solve this issue?
Use sudo instead of su. See also
http://stackoverflow.com/questions/10227590/change-user-in-linux-script

--hymie! http://lactose.homelinux.net/~hymie ***@lactose.homelinux.net
Helmut Waitzmann
2017-03-14 05:54:47 UTC
Permalink
Post by Hongyi Zhao
Hi all,
I try to use su to execute some commands with heredoc in my script like
su root <<EOF
bla
bla
...
EOF
As there is no means to tell su to redirect standard input to file
descriptor #3 after having read the password from standard input
but before executing the shell, the redirection has to be done by
the shell that is executed by su.

As far as I understand one can't tell a shell to redirect file
descriptor #0 to file descriptor #3 and then read commands from
standard input.

But one can tell a shell to redirect descriptor #0 to file
descriptor #3 and then start a new shell.

Assuming, that the root account's shell is a POSIX-conformant
shell,

su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' \
su 3<<EOF

will provide the here document to the su command at file
descriptor #3, thus preserving file descriptor #0 to remain the
terminal. This enables su to ask for the password at file
descriptor #0.

The shell that is spawned from su will now redirect file
descriptor #0 to file descriptor #3, close file descriptor #3, and
start a new shell.

Examples:

Let a root shell read commands from the here document:

su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' \
su optional shell arguments 3<<EOF
bla
bla
...
EOF

Let a root shell execute a command line with standard input
redirected to the here document:

su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' \
su -c ' a_command_line' with optional arguments 3<<EOF
bla
bla
...
EOF
Hongyi Zhao
2017-03-14 07:00:35 UTC
Permalink
On Tue, 14 Mar 2017 06:54:47 +0100, Helmut Waitzmann wrote:

[snipped]
Post by Helmut Waitzmann
su 3<<EOF
Thanks a lot for your deep-in analysis and wonderful solution.

Still I've some confusions on the above code given by you:

[1] About the `--' used by you here. Based on the manual page of su:

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

According the above description, does it means that we should use the
following form in your above code:

su root -- -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' \
su 3<<EOF

[2] If I use the `-m, -p, --preserve-environment' option, does it means
that the "$SHELL" will not be needed?

[3] If I also use `-' option, based on the manual page of su:

-, -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.

What form should I used for your above code?

[4] Why must use two `su' in your code for this type of job?

Regards

[snipped]
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-15 15:48:38 UTC
Permalink
Post by Hongyi Zhao
[snipped]
Post by Helmut Waitzmann
su 3<<EOF
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 Zhao
According 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,
Post by Hongyi Zhao
Post by Helmut Waitzmann
su 3<<EOF
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 Zhao
What 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
Hongyi Zhao
2017-03-15 23:24:38 UTC
Permalink
Post by Helmut Waitzmann
So, maybe, it's better to explicitly specify what shell to run,
^^^^^
Do you mean:

su -- root -c 'exec 0<&3 3<&- && bash ${1+"$@"}' su
^^^^
Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-16 00:10:47 UTC
Permalink
Post by Helmut Waitzmann
So, maybe, it's better to explicitly specify what shell to run,
^^^^^
Yes. Thank you for the correction.
Hongyi Zhao
2017-03-15 23:49:00 UTC
Permalink
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.
I inspected my su's manual page, it doesn't have any descriptions you
given above at all.

I use the su which shipped with Debian Jessie the you can see the info of
my os:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.7 (jessie)
Release: 8.7
Codename: jessie

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-16 00:37:57 UTC
Permalink
Post by Hongyi Zhao
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.
I inspected my su's manual page, it doesn't have any descriptions you
given above at all.
Yes. Not all “su” in all unices do that.

To examine, what your su will do, you could run the command

su -- "$LOGNAME" -c '"$@" "$$" && :' sh ps -o args

to get a non-login shell running ps, and

su -- - "$LOGNAME" -c '"$@" "$$" && :' -sh ps -o args

to get a login shell running ps.
Hongyi Zhao
2017-03-16 02:40:59 UTC
Permalink
Post by Helmut Waitzmann
Yes. Not all “su” in all unices do that.
Why does they -- the developers of these tools -- let things like this?

This will make the tool more difficult for user, like me, to use it.
Post by Helmut Waitzmann
To examine, what your su will do, you could run the command
to get a non-login shell running ps, and
to get a login shell running ps.
If so, maybe the only way to know the details of this feature is based on
inspecting of the source code.

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-17 07:25:01 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
Yes. Not all “su” in all unices do that.
Why does they -- the developers of these tools -- let things like this?
I don't know.
Post by Hongyi Zhao
This will make the tool more difficult for user, like me, to use it.
Post by Helmut Waitzmann
To examine, what your su will do, you could run the command
to get a non-login shell running ps, and
to get a login shell running ps.
What will the following two commands output, when you run them on
your system?

su -- root -c '
ps --cols=66 -o comm=FILE -o args "$$" &&
printf '\''"$0"%s\n'\'' "${0+=}${0- undefined}"' \
sh

su -- - root -c '
ps --cols=66 -o comm=FILE -o args "$$" &&
printf '\''"$0"%s\n'\'' "${0+=}${0- undefined}"' \
-sh

They show (by means of the command “ps”) the name of the
executable file of the running shell (column FILE), and the
invocation parameters (column COMMAND) given to it. Also the
shell special parameter “$0” (last line of output) is shown.

On my system I get

Password:
FILE COMMAND
bash bash -c ? ps --cols=66 -o comm=FILE -o args "$$
"$0"=sh

and

Password:
FILE COMMAND
bash -su -c ? ps --cols=66 -o comm=FILE -o args "$$"
"$0"=-sh

respectively.

* If a non-login shell (in this example: a bash, see column FILE)
is started by su, su supplies to the shell the invocation name
“bash” (see column COMMAND).

* Whereas, if a login shell (in this example, a bash) is started
by su, su supplies to the shell the invocation name “-su” rather
than “-bash”.
Post by Hongyi Zhao
If so, maybe the only way to know the details of this feature is based on
inspecting of the source code.
If one wants to be absolutely sure, yes.
Hongyi Zhao
2017-03-17 23:11:05 UTC
Permalink
Post by Helmut Waitzmann
On my system I get
FILE COMMAND bash bash -c ? ps --cols=66 -o
^^^
I don't get the `?' for my case.
Post by Helmut Waitzmann
comm=FILE -o args "$$
"$0"=sh
See my testing:

$ su -- root -c '
ps --cols=66 -o comm=FILE -o args "$$" &&
printf '\''"$0"%s\n'\'' "${0+=}${0- undefined}"' \
sh
Password:
FILE COMMAND
bash bash -c ps --cols=66 -o comm=FILE -o args "$$
"$0"=sh
Post by Helmut Waitzmann
and
FILE COMMAND bash -su -c ? ps --cols=66 -o
I don't get the `?' for my case.
Post by Helmut Waitzmann
comm=FILE -o args "$$"
"$0"=-sh
See my testing:

$ su -- - root -c '
ps --cols=66 -o comm=FILE -o args "$$" &&
printf '\''"$0"%s\n'\'' "${0+=}${0- undefined}"' \
-sh
Password:
FILE COMMAND
bash -su -c ps --cols=66 -o comm=FILE -o args "$$"
"$0"=-sh
Post by Helmut Waitzmann
respectively.
Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-18 01:05:26 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
On my system I get
FILE COMMAND bash bash -c ? ps --cols=66 -o
^^^
I don't get the `?' for my case.
I assume, the “?” stands for the newline character in the command
line, but that doesn't matter.
Post by Hongyi Zhao
$ su -- root -c '
ps --cols=66 -o comm=FILE -o args "$$" &&
printf '\''"$0"%s\n'\'' "${0+=}${0- undefined}"' \
sh
FILE COMMAND
bash bash -c ps --cols=66 -o comm=FILE -o args "$$
"$0"=sh
The bash (column FILE) has been invoked by su with the invocation
name “bash” (column COMMAND; see the parameter “arg0” in the
synopsis of the library function execlp() in
<http://pubs.opengroup.org/onlinepubs/9699919799/functions/execlp.html>).

[…]
Post by Hongyi Zhao
$ su -- - root -c '
ps --cols=66 -o comm=FILE -o args "$$" &&
printf '\''"$0"%s\n'\'' "${0+=}${0- undefined}"' \
-sh
FILE COMMAND
bash -su -c ps --cols=66 -o comm=FILE -o args "$$"
"$0"=-sh
The bash (column FILE) has been invoked by su with the invocation
name “-su” (column COMMAND; see the parameter “arg0” in the
synopsis of the library function execlp() in
<http://pubs.opengroup.org/onlinepubs/9699919799/functions/execlp.html>).
Hongyi Zhao
2017-03-17 23:20:31 UTC
Permalink
Post by Helmut Waitzmann
su -- root -c '
ps --cols=66 -o comm=FILE -o args "$$" &&
printf '\''"$0"%s\n'\'' "${0+=}${0- undefined}"' \
sh
Why you use this complex variable expansion method in the above code:

"${0+=}${0- undefined}"

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-18 01:21:18 UTC
Permalink
Post by Helmut Waitzmann
Post by Helmut Waitzmann
su -- root -c '
ps --cols=66 -o comm=FILE -o args "$$" &&
printf '\''"$0"%s\n'\'' "${0+=}${0- undefined}"' \
sh
"${0+=}${0- undefined}"
to get a shorter command line ;)

If the positional parameter $0 is undefined, "${0+=}" yields the
empty string, and "${0- undefined}" yields the string
“ undefined”. So the command

printf '"$0"%s\n' "${0+=}${0- undefined}"

prints the line “"$0" undefined”.


If the positional parameter $0 is defined, "${0+=}" yields an
equal sign, and "${0- undefined}" yields $0's value. So the
command

prints a line consisting of “"$0"=<value of $0>”.

An alternative would have been the command

if test -n "${0+defined}"
then
printf '"$0"%s\n' "=$0"
else
printf '"$0"%s\n' ' undefined'
fi
Barry Margolin
2017-03-17 16:06:38 UTC
Permalink
Post by Hongyi Zhao
Yes. Not all “su” in all unices do that.
Why does they -- the developers of these tools -- let things like this?
This will make the tool more difficult for user, like me, to use it.
Different Unix implementation have different sets of developers. When
one developer decides to add a new feature, it doesn't get into all the
other distributions, and you end up with divergence.

Sometimes things get back into sync when the other developers notice a
feature that they like, and they copy it into their version as well. But
that takes time, so they'll never be completely the same.

Other times, different developers make changes that are incompatible
with each other. These are hard to reconcile.

The most notorious case is the "ps" command, which had very different
arguments on BSD and AT&T Unix. They've managed to be reconciled
somewhat, because the AT&T options didn't begin with "-" while the BSD
options did (or something like that).
--
Barry Margolin, ***@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
Casper H.S. Dik
2017-03-17 17:09:56 UTC
Permalink
Post by Barry Margolin
The most notorious case is the "ps" command, which had very different
arguments on BSD and AT&T Unix. They've managed to be reconciled
somewhat, because the AT&T options didn't begin with "-" while the BSD
options did (or something like that).
Vice versa (ucb ps did not require a leading "-" and AT&T did require
that.

So these days when you use "ps uxga" on Solaris you'd get BSD behaviour
and when you use "ps -ef" you'd get SVr4 behaviour (and ps -uxga will give
you an error that xga is an unknown user)

Casper
Hongyi Zhao
2017-03-16 05:32:04 UTC
Permalink
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 “-”
-su 3<<EOF
The “-” is the first non-option argument, because “--” explicitly
terminates option processing.
I do the following testings based on your above codes:

$ ls
setup-oh-my-stow.bash stow_dir test
$

$ su -- - root -c 'exec 0<&3 3<&- && bash ${1+"$@"}' \
-su 3<<EOF
ls
EOF
Password:
$


$ su -- root -c 'exec 0<&3 3<&- && bash ${1+"$@"}' \
-su 3<<EOF
ls
EOF

Password:
setup-oh-my-stow.bash stow_dir test
$

As you can see, the former form, i.e., the one your suggested here, will
give nothing when feeding the `ls' command to it. While the latter form
which doesn't use the the `-' symbol just before the username, will run
the `ls' command correctly.

Why does this happen?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-17 07:26:47 UTC
Permalink
Post by Hongyi Zhao
$ ls
setup-oh-my-stow.bash stow_dir test
$
-su 3<<EOF
ls
EOF
$
-su 3<<EOF
ls
EOF
setup-oh-my-stow.bash stow_dir test
$
As you can see, the former form, i.e., the one your suggested here, will
give nothing when feeding the `ls' command to it. While the latter form
which doesn't use the the `-' symbol just before the username, will run
the `ls' command correctly.
Why does this happen?
On my system, “su” not only has got a manual page but also an info
page:

info su

There is one strange thing: The contents of the info page differs
From that of the manual page.

From the info page:

`-'
`-l'
`--login'
Make the shell a login shell. This means the following.
Unset all environment variables except `TERM', `HOME', and
`SHELL' (which are set as described above), and `USER' and
`LOGNAME' (which are set, even for the super-user, as
described above), and set `PATH' to a compiled-in default
value. Change to USER's home directory. Prepend `-' to the
shell's name, intended to make it read its login startup
file(s).

“Change to USER's home directory.” Therefore the “ls” command
to be executed will get root's home directory as the current
working directory.
Hongyi Zhao
2017-03-16 06:33:45 UTC
Permalink
If you want use this form, why use the ``-s'' option of bash:

------------- begin excerpted from bash's manual page ---------------
-s If the -s option is present, or if no arguments remain after
option processing, then commands are read from the standard
input. This option allows the positional parameters to be
set when invoking an interactive shell.
------------ end excerpted from bash's manual page ------------------

Based on the above description, what about change your above code into
the following form:

# you have clarified that `-su' should be `su' here:
su -- root -c 'exec 0<&3 3<&- && bash -s' su

Regards
Post by Helmut Waitzmann
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.
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-17 07:46:26 UTC
Permalink
I'm not sure, if I understand you correctly. Do you mean the last
word, “-su”, of the command?

su -- root -c 'exec 0<&3 3<&- && bash ${1+"$@"}' -su

This command will start a non-login root shell, giving the following
parameters to it:

The invocation name of the shell will be the shells executable
file name with all leading directory components removed, that is:

If root's shell is a “/bin/bash”, the invocation name will be
“bash”. If root's shell is a “/bin/sh”, the invocation name will
be “sh”.

(See my posting from 2017-03-17T08:24:35+0100
<news:***@helmutwaitzmann.news.arcor.de> with message
id <***@helmutwaitzmann.news.arcor.de>.)

The first argument given to that shell will be “-c”, the second
one given will be “exec 0<&3 3<&- && bash ${1+"$@"}”, and the
third one given will be “-su”:

"$shell" -c 'exec 0<&3 3<&- && bash ${1+"$@"}' -su

The first option in the shell's argument list is “-c”. The next
argument “exec 0<&3 3<&- && bash ${1+"$@"}” is a non-option
argument, because it does not start with a hyphen. So option
processing stops here. Therefore the third argument “-su”,
although starting with a hyphen, is not considered by the shell to
be the options “-s” (read commands from standard input) and “-u”
(treat the expansion of unset shell variables as an error). It is
merely the parameter called “command_name” in the invocation
synopsis

sh -c [-abCefhimnuvx] [-o option]... [+abCefhimnuvx] [+o option]...
command_string [command_name [argument...]]

of the shell (see
<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html>)
or the shell's manual page.
Post by Hongyi Zhao
------------- begin excerpted from bash's manual page ---------------
-s If the -s option is present, or if no arguments remain after
option processing, then commands are read from the standard
input. This option allows the positional parameters to be
set when invoking an interactive shell.
------------ end excerpted from bash's manual page ------------------
Based on the above description, what about change your above code into
su -- root -c 'exec 0<&3 3<&- && bash -s' su
Of course you can do that, if you like.

But my intension was to provide a general means of “read the su
password from standard input, then redirect file descriptor #3 to
standard input and start a shell”.

Using this idiom, one can

* let that shell read commands from standard input by supplying
the option “-s” to the shell:

su -- root -c 'exec 0<&3 3<&- && bash ${1+"$@"}' su \
-s optional positional parameters

* let that shell read commands from a shell script:

su -- root -c 'exec 0<&3 3<&- && bash ${1+"$@"}' su \
a_shell_script.sh optional positional parameters

* let that shell read a command line via an option “-c”:

su -- root -c 'exec 0<&3 3<&- && bash ${1+"$@"}' su \
-c 'a command line' bash optional positional parameters

The “bash ${1+"$@"}” part of the root shell's command line lets
the root shell start a bash, supplying it all remaining su
parameters.

This freedom to use it one way or another is not given, if one
hardcodes the “-s” in the invocation of the bash:

su -- root -c 'exec 0<&3 3<&- && bash -s' su

But YMMV.
Hongyi Zhao
2017-03-17 13:09:54 UTC
Permalink
^^^^^
Sorry for my typo in the above sentence, I just want to say the following:

If you want use this form, why not use the ``-s'' option of bash:
^^^^^^^
I'm not sure, if I understand you correctly. Do you mean the last word,
“-su”, of the command?
No, I mean changing your code into the following:

su -- root -c 'exec 0<&3 3<&- && bash -s' -su
Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Hongyi Zhao
2017-03-14 10:34:46 UTC
Permalink
Post by Helmut Waitzmann
su 3<<EOF
Based on your above code, I do the following testing:

Firstly, I created a directory named test with two files in it:

$ find ./test -type f
./test/111
./test/222

Then I put the following code in a script to do the testing:

test-su.bash
-----------
#!/usr/bin/env bash

topdir=$(
cd -P -- "$(dirname -- "$0")" &&
pwd -P
)

cd $topdir

su -- root -p -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' \
su 3<<EOF
find ./test -type f |
while IFS= read -r line; do
echo "the file under test dir is: $line"
done
EOF
-----------

Then I put the above script in the parent directory of test directory and
run it:

$ bash setup-oh-my-stow.bash
Password:
the file under test dir is:
the file under test dir is:
^C
$

As you can see, in the latter case, the `read' command in the while loop
failed to obtain the output of find.

Any hints for this issue?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-15 15:49:09 UTC
Permalink
Post by Helmut Waitzmann
Post by Helmut Waitzmann
su 3<<EOF
[…]
Post by Helmut Waitzmann
su 3<<EOF
find ./test -type f |
while IFS= read -r line; do
echo "the file under test dir is: $line"
done
EOF
[…]
Post by Helmut Waitzmann
$ bash setup-oh-my-stow.bash
^C
$
You discovered the difference between

“some_command <<EOF
$line
EOF”

and

“some_command <<'EOF'
$line
EOF”:

In the former case, text like “$line” in the here document is
parameter-expanded by the shell that processes the “<<EOF”
redirection. In the latter case, it's used verbatim. (See also
<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04>
for the whole story.)

To test that, compare the command

---CUT-HERE
line="What's going on here?"
cat <<'EOF'
find ./test -type f |
while IFS= read -r line; do
echo "the file under test dir is: $line"
done
EOF
---CUT-HERE

with the command

---CUT-HERE
line="What's going on here?"
cat <<'EOF'
find ./test -type f |
while IFS= read -r line; do
echo "the file under test dir is: $line"
done
EOF
---CUT-HERE
Hongyi Zhao
2017-03-15 23:40:24 UTC
Permalink
Post by Helmut Waitzmann
To test that, compare the command
---CUT-HERE line="What's going on here?"
cat <<'EOF'
find ./test -type f |
while IFS= read -r line; do
echo "the file under test dir is: $line"
done EOF ---CUT-HERE
with the command
---CUT-HERE line="What's going on here?"
cat <<'EOF'
find ./test -type f |
while IFS= read -r line; do
echo "the file under test dir is: $line"
done EOF ---CUT-HERE
Both you above two tests using: 'EOF', so it must be your typo.

One should using 'EOF' and the other using EOF.

Thanks a lot.

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-16 00:42:09 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
To test that, compare the command
---CUT-HERE line="What's going on here?"
cat <<'EOF'
find ./test -type f |
while IFS= read -r line; do
echo "the file under test dir is: $line"
done EOF ---CUT-HERE
with the command
---CUT-HERE line="What's going on here?"
cat <<'EOF'
find ./test -type f |
while IFS= read -r line; do
echo "the file under test dir is: $line"
done EOF ---CUT-HERE
Both you above two tests using: 'EOF', so it must be your typo.
One should using 'EOF' and the other using EOF.
Oh, sorry. You are right. One should use “<<'EOF'” and the other
should use “<<EOF”. (Both of them should use “EOF” in the last
line of the script.)
Hongyi Zhao
2017-03-16 06:25:20 UTC
Permalink
Post by Helmut Waitzmann
su 3<<EOF
Another issue is when using the above form, I can obtain the variables
value defined outside of the heredoc; while using the delimiter form
<<'EOF', it will prevent the variable expansion and I won't be able to
obtain the variables value defined outside of the heredoc, see my
following testings:

$ aa=123
$ su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su 3<<
\EOF
echo $aa
EOF
Password:

$ su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su 3<<EOF
echo $aa
EOF

Password:
123
$

So, my issue is: is it possible for me to ensure meta-characters are not
expanded for certain part of the heredoc and at the meanwhile, let me can
obtain the variables value defined outside of the heredoc in my script,
if required?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Hongyi Zhao
2017-03-16 07:12:35 UTC
Permalink
Post by Hongyi Zhao
So, my issue is: is it possible for me to ensure meta-characters are not
expanded for certain part of the heredoc and at the meanwhile, let me
can obtain the variables value defined outside of the heredoc in my
script, if required?
After some tries on this issue, I think the most feasible way should be
the following:

$ aa=123
$ su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su 3<<EOF
# this should be expanded:
echo $aa
# this shouldn't be expanded:
bla \$some_other_var
EOF

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-17 08:14:33 UTC
Permalink
Post by Hongyi Zhao
Post by Hongyi Zhao
So, my issue is: is it possible for me to ensure meta-characters are not
expanded for certain part of the heredoc and at the meanwhile, let me
can obtain the variables value defined outside of the heredoc in my
script, if required?
After some tries on this issue, I think the most feasible way should be
$ aa=123
echo $aa
bla \$some_other_var
EOF
Beware! What will happen, if you do the following command?
(Don't run it, or you will need your backup!)

$ aa=';cd;rm -Rf'
$ su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su 3<<EOF
# this should be expanded:
echo $aa
# this shouldn't be expanded:
bla \$some_other_var
EOF

Or rather than start a "$SHELL" by the means of that dangerous

If you want to see, what that dangerous command would do, if you
ran it, just “cat” standard input by replacing the dangerous “su”
command line above

su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su 3<<EOF

by

su -- root -c 'exec 0<&3 3<&- && cat' su 3<<EOF

and run it. This will just print the commands rather than let a
shell execute them.

For a safe way to import variables, see my posting
<news:***@helmutwaitzmann.news.arcor.de> with message
id <***@helmutwaitzmann.news.arcor.de> from
2017-03-17T08:57:37+0100.
Helmut Waitzmann
2017-03-17 08:41:42 UTC
Permalink
Post by Hongyi Zhao
Post by Hongyi Zhao
So, my issue is: is it possible for me to ensure meta-characters are not
expanded for certain part of the heredoc and at the meanwhile, let me
can obtain the variables value defined outside of the heredoc in my
script, if required?
After some tries on this issue, I think the most feasible way should be
$ aa=123
echo $aa
bla \$some_other_var
EOF
Beware! What will happen, if you do the following command?
(Don't run it, or you will need your backup!)

$ aa=';cd;rm -Rf'
$ su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su 3<<EOF
# this should be expanded:
echo $aa
# this shouldn't be expanded:
bla \$some_other_var
EOF

If you want to see, what that dangerous command would do, if you
ran it, just “cat” standard input by replacing the dangerous “su”
command line above

su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su 3<<EOF

by

su -- root -c 'exec 0<&3 3<&- && cat' su 3<<EOF

and run it. This will just print the commands rather than let a
shell execute them.

For a safe way to import variables, see my posting
<news:***@helmutwaitzmann.news.arcor.de> with message
id <***@helmutwaitzmann.news.arcor.de> from
2017-03-17T08:57:37+0100.
Hongyi Zhao
2017-03-17 14:00:38 UTC
Permalink
Post by Helmut Waitzmann
For a safe way to import variables, see my posting
2017-03-17T08:57:37+0100.
Why not post you reply directly to this group?

In addition, I use Pan 0.141, it seems it can't directly locate/open your
above message based on the message id your applied here.

I then searched on the google and find the following website which can do
the ``Usenet Article Lookup'' based on the message id:

http://al.howardknight.net/

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Hongyi Zhao
2017-03-17 14:04:31 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
For a safe way to import variables, see my posting
2017-03-17T08:57:37+0100.
Why not post you reply directly to this group?
Sorry, I see your reply on this thread later then I see the above notes
by you.

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-17 07:58:03 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
su 3<<EOF
Another issue is when using the above form, I can obtain the variables
value defined outside of the heredoc; while using the delimiter form
<<'EOF', it will prevent the variable expansion and I won't be able to
obtain the variables value defined outside of the heredoc, see my
$ aa=123
\EOF
echo $aa
EOF
echo $aa
EOF
123
$
So, my issue is: is it possible for me to ensure meta-characters are not
expanded for certain part of the heredoc and at the meanwhile, let me can
obtain the variables value defined outside of the heredoc in my script,
if required?
Yes. You can do

su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su \
-s \
zero or more variable/value pairs -- \
optional positional parameters \
3<<\EOF
while test -n "${1+defined}"
do
case "$1" in
?*)
if test -z "${2+defined}"
then
printf >&2 \
'%s: missing value for variable %s\n' \
"$0" "$1"
exit 1
fi
eval "${1:?}="'"${2?}"'
shift 2;;
--)
# end of variable list: stop processing variables
shift; break;;
'')
printf >&2 \
'%s: The null string is not a variable name.\n' \
"$0"
exit 1;;
esac
done
# all variable value pairs have been processed
EOF

Example:

aa=123
su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su \
-s aa "$aa" bb ';cd /;rm -Rf "$HOME"' -- \
3<<\EOF
while test -n "${1+defined}"
do
case "$1" in
?*)
if test -z "${2+defined}"
then
printf >&2 '%s: missing value for variable %s\n' \
"$0" "$1"
exit 1
fi
eval "${1:?}="'"${2?}"'
shift 2;;
--)
# end of variable list
shift; break;;
'')
printf >&2 \
'%s: The null string is not a variable name.\n' \
"$0"
exit 1;;
esac
done
echo $aa $bb
EOF

Notes:

Ad-hoc variables can be supplied, too, as can be seen with
variable “bb”: Variable “bb” will be defined by the "$SHELL" that
will execute the contents of the here document, even if it isn't
defined in the shell that is processing the “<<\EOF” redirection.

Even variables, whose contents look dangerous, can be supplied
without harm, as can be seen with variable “bb”.
Hongyi Zhao
2017-06-13 00:07:59 UTC
Permalink
Post by Hongyi Zhao
aa=123
-s aa "$aa" bb ';cd /;rm -Rf "$HOME"' -- \
3<<\EOF while test -n "${1+defined}"
do
case "$1" in
?*)
if test -z "${2+defined}"
then
printf >&2 '%s: missing value for variable %s\n' \
"$0" "$1"
exit 1
fi eval "${1:?}="'"${2?}"' shift 2;;
--)
# end of variable list shift; break;;
'')
printf >&2 \
'%s: The null string is not a variable name.\n' \
"$0"
exit 1;;
esac
done echo $aa $bb
EOF
I save the above script into a file named as pass-var-into-su.sh and then
test it, but obtaining the following output:

$ bash pass-var-into-su.sh
Password:
/bin/bash: missing value for variable --

I cann't see any expanding of the var's value.

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-06-13 05:42:09 UTC
Permalink
Post by Hongyi Zhao
Post by Hongyi Zhao
aa=123
-s aa "$aa" bb ';cd /;rm -Rf "$HOME"' -- \
3<<\EOF while test -n "${1+defined}"
do
case "$1" in
?*)
if test -z "${2+defined}"
then
printf >&2 '%s: missing value for variable %s\n' \
"$0" "$1"
exit 1
fi eval "${1:?}="'"${2?}"' shift 2;;
--)
# end of variable list shift; break;;
'')
printf >&2 \
'%s: The null string is not a variable name.\n' \
"$0"
exit 1;;
esac
done echo $aa $bb
EOF
I save the above script into a file named as pass-var-into-su.sh and then
$ bash pass-var-into-su.sh
/bin/bash: missing value for variable --
Yes, that was my fault. In the case construct, the pattern “--”
should precede the pattern “?*”:

while test -n "${1+defined}"
do
case "$1" in
--)
# end of variable list: stop processing variables
shift; break;;
?*)
if test -z "${2+defined}"
then
printf >&2 \
'%s: missing value for variable %s\n' \
"$0" "$1"
exit 1
fi
eval "${1:?}="'"${2?}"'
shift 2;;
'')
printf >&2 \
'%s: The null string is not a variable name.\n' \
"$0"
exit 1;;
esac
done

To get it even more robust, I think, the assignments should be
passed by “variable=value” parameters rather than “variable value”
pairs of parameters:

while test "$#" -ge 1
do
case "$1" in
--)
# end of variable list: stop processing variables
shift; break;;
?*=*)
eval "${1%%=*}="'"${1#*=}"'
shift;;
?*)
printf >&2 \
'%s: %s is not a variable assignment.\n' \
"$0" "$1"
exit 1;;
esac
done

You can use it like this:

aa=123
su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su \
-s aa="$aa" bb='some value' -- some positional parameters \
3<<\EOF
while test "$#" -ge 1
do
case "$1" in
--)
# end of variable list: stop processing variables
shift; break;;
?*=*)
eval "${1%%=*}="'"${1#*=}"'
shift;;
?*)
printf >&2 \
'%s: %s is not a variable assignment.\n' \
"$0" "$1"
exit 1;;
esac
done
printf '%s\n' aa="$aa" bb="$bb" 'positional parameters:' "$@"
EOF
Hongyi Zhao
2017-06-14 00:58:34 UTC
Permalink
Post by Helmut Waitzmann
eval "${1%%=*}="'"${1#*=}"'
Why must use both single and double quotation marks here?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Kaz Kylheku
2017-06-14 01:12:47 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
eval "${1%%=*}="'"${1#*=}"'
Why must use both single and double quotation marks here?
Because there are two rounds of evaluation going on.
We have an eval command which is evaluated.
That eval causes its arguments to be evaluated again:

eval "${1%%=*}="'"${1#*=}"'
^^^^^^^^^^^

This syntax needs to be expanded right away,
so that the expansion is passed to eval:
the expansion is a calculated variable name
followed by an equal sign, like FOO=


eval "${1%%=*}="'"${1#*=}"'
^^^^^^^^^

This syntax must pass verbatim to the
second evaluation round. So it is
wrapped in single quotes. Thus the eval
will evaluate the computed assignment
having a form like: FOO="${1#*=}"
Hongyi Zhao
2017-03-16 06:38:37 UTC
Permalink
Assuming, that the root account's shell is a POSIX-conformant shell,
su 3<<EOF
Does the same trick also applied to the `sudo' utility? I mean, can I
also use the similar form as follows for my job:

sudo -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' \
sudo 3<<EOF

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-17 08:38:37 UTC
Permalink
Post by Hongyi Zhao
Assuming, that the root account's shell is a POSIX-conformant shell,
su 3<<EOF
Does the same trick also applied to the `sudo' utility? I mean, can I
Sudo differes widely from su, so the command
Post by Hongyi Zhao
sudo 3<<EOF
won't work.

As sudo opens „/dev/tty” on its own to read the password, it does
not use standard input to do that. Therefore there is almost
never any need to supply the standard input for the shell via file
descriptor #3: One can just use standard input.

The command

su -- - root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' -su \
-s 3<<EOF

would roughly correspond to the command

sudo -u root -i sh -c 'exec "$SHELL" ${1+"$@"}' sh \
-s <<\EOF

to let a login shell start a "$SHELL" that reads commands from
standard input.

The command

su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' \
su -s 3<<EOF

hasn't got any equivalent with sudo, as it is impossible to let
sudo set the environment variable “SHELL” to the target user's
shell (as specified in the user database) without starting a login
shell (if I understand correctly).

(But I think, that's no deficiency, because setting up a proper
environment by means of a login shell is almost always a good
idea.)

With sudo, you /can/ supply the password via standard input and
the payload data via a different file descriptor, though (for
example, if you want to supply the password not via the terminal
but via a pipe), if you use the sudo options “-S” and “-C” (see
the manual page sudo(8)) and have specified the
“closefrom_override” or “closefrom” options in the file
“/etc/sudoers” (see the manual page sudoers(5)).
Hongyi Zhao
2017-03-17 14:30:45 UTC
Permalink
Post by Helmut Waitzmann
The command
-s 3<<EOF
Perhaps I don't understand your purpose correctly here; but based on your
above code, I do the following testing:

$ su -- - root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' -su -s 3<<EOF
ls
Post by Helmut Waitzmann
EOF
Password:
$

As you can see, it doesn't execute the ``ls'' command at all.
Post by Helmut Waitzmann
would roughly correspond to the command
-s <<\EOF
Also, see my following testing on my Debian Jessie with your above code:

$ sudo -u root -i sh -c 'exec "$SHELL" ${1+"$@"}' sh -s <<\EOF
ls
EOF
stdin: is not a tty
$

Why?
Post by Helmut Waitzmann
to let a login shell start a "$SHELL" that reads commands from standard
input.
Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-17 23:11:05 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
The command
-s 3<<EOF
Perhaps I don't understand your purpose correctly here; but based on your
ls
Post by Helmut Waitzmann
EOF
$
As you can see, it doesn't execute the ``ls'' command at all.
Perhaps your directory is empty?

Please try the following command:

$ PS2='? '
$ su -- - root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' -su -s 3<<\EOF
? ls -d -- "${HOME%/}"/
? EOF
Password:
/root/
Post by Hongyi Zhao
Post by Helmut Waitzmann
would roughly correspond to the command
-s <<\EOF
ls
EOF
stdin: is not a tty
$
Why?
Hm. That differes from the behavior of my sudo:

$ PS2='? '
$ sudo -u root -i sh -c 'exec "$SHELL" ${1+"$@"}' sh -s <<\EOF
? pwd
? EOF
[sudo] password for helmut:
/root

Would you send me your sudo(8) and sudoers(5) manual pages, please?
Hongyi Zhao
2017-03-17 23:39:58 UTC
Permalink
Post by Helmut Waitzmann
Perhaps your directory is empty?
Yes, It only have dot-files in the directory:

$ sudo ls -la /root/
total 40
drwx------ 7 root root 4096 Mar 11 23:17 .
drwxr-xr-x 22 root root 4096 Mar 11 12:40 ..
-rw------- 1 root root 1481 Mar 16 17:23 .bash_history
-rw-r--r-- 1 root root 570 Jan 31 2010 .bashrc
drwx------ 3 root root 4096 Mar 11 22:54 .cache
drwxr-xr-x 5 root root 4096 Mar 11 23:05 .config
drwx------ 3 root root 4096 Mar 11 22:54 .dbus
drwxr-xr-x 3 root root 4096 Mar 11 22:54 .local
-rw-r--r-- 1 root root 140 Nov 20 2007 .profile
drwx------ 3 root root 4096 Mar 16 21:09 .synaptic
$
Post by Helmut Waitzmann
$ PS2='? '
? ls -d -- "${HOME%/}"/
/root/
This time I get the same result as yours:

$ PS2='? '
$ su -- - root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' -su -s 3<<\EOF
? ls -d -- "${HOME%/}"/
? EOF
Password:
/root/
Post by Helmut Waitzmann
Post by Helmut Waitzmann
would roughly correspond to the command
-s <<\EOF
stdin: is not a tty $
Why?
$ PS2='? '
/root
See my results:

$ PS2='? '
$ sudo -u root -i sh -c 'exec "$SHELL" ${1+"$@"}' sh -s <<\EOF
? pwd
? EOF
stdin: is not a tty
/root
Post by Helmut Waitzmann
Would you send me your sudo(8) and sudoers(5) manual pages, please?
See here:
https://gist.github.com/hongyi-zhao/465d8fd038197cd2dedb54b57a9de5e0
https://gist.github.com/hongyi-zhao/ef3c5dd65c45740a20169a4825815470

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Hongyi Zhao
2017-03-18 00:03:21 UTC
Permalink
Post by Helmut Waitzmann
$ PS2='? '
? ls -d -- "${HOME%/}"/
/root/
But, when I using su's ``-p'' option, I still obtain the same result as
above:

$ echo $HOME
/home/werner


$ PS2='? '
$ su -- - root -p -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' -su -s 3<<\EOF
? ls -d -- "${HOME%/}"/
? EOF
Password:
/root/

Based on the manual page of my su:

---------------------
-m, -p, --preserve-environment
Preserve the current environment, except for:

$PATH
reset according to the /etc/login.defs options ENV_PATH or
ENV_SUPATH (see below);

$IFS
reset to “<space><tab><newline>”, if it was set.

If the target user has a restricted shell, this option has no
effect (unless su is called by root).

Note that the default behavior for the environment is the
following:

The $HOME, $SHELL, $USER, $LOGNAME, $PATH, and $IFS
environment
variables are reset.
-------------------------

I wonder why the current $HOME, i.e., /home/werner for my case, can't be
preserved when using the ``-p'' option.

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-18 02:13:08 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
$ PS2='? '
? ls -d -- "${HOME%/}"/
/root/
But, when I using su's ``-p'' option, I still obtain the same result as
$ echo $HOME
/home/werner
I'll not test this on my system, as I consider running a login
shell without resetting the HOME environment variable to the
target user's home directory an unsane thing to do:

For example, let root run a login shell with HOME set to my home
directory will cause the login shell to read the “.profile” in
/my/ home directory rather than in root's. Also, if the login
shell writes a history file, the history file will be written to
my home directory. As it is written by a root process, it will be
unaccessible to shells running as my user id because of lack of
permissions.

Sorry, I won't shoot me in my foot.
Post by Hongyi Zhao
$ PS2='? '
? ls -d -- "${HOME%/}"/
? EOF
/root/
---------------------
-m, -p, --preserve-environment
$PATH
reset according to the /etc/login.defs options ENV_PATH or
ENV_SUPATH (see below);
$IFS
reset to “<space><tab><newline>”, if it was set.
If the target user has a restricted shell, this option has no
effect (unless su is called by root).
Note that the default behavior for the environment is the
The $HOME, $SHELL, $USER, $LOGNAME, $PATH, and $IFS
environment
variables are reset.
-------------------------
I wonder why the current $HOME, i.e., /home/werner for my case, can't be
preserved when using the ``-p'' option.
Maybe, because of the risk of shooting oneself in the foot.
Helmut Waitzmann
2017-03-18 01:46:31 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
Would you send me your sudo(8) and sudoers(5) manual pages, please?
https://gist.github.com/hongyi-zhao/465d8fd038197cd2dedb54b57a9de5e0
https://gist.github.com/hongyi-zhao/ef3c5dd65c45740a20169a4825815470
They are unusable for my manual pages browser.

Would you please

run the command

man --location -- 8 sudo 5 sudoers

and send me the named files?
Hongyi Zhao
2017-03-18 08:02:44 UTC
Permalink
Post by Helmut Waitzmann
Would you please
run the command
man --location -- 8 sudo 5 sudoers
$ man --location -- 8 sudo 5 sudoers
/usr/share/man/man8/sudo.8.gz
/usr/share/man/man5/sudoers.5.gz
Post by Helmut Waitzmann
and send me the named files?
Could you please tell my email and I can send them to you.

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-18 19:34:01 UTC
Permalink
Post by Hongyi Zhao
$ man --location -- 8 sudo 5 sudoers
/usr/share/man/man8/sudo.8.gz
/usr/share/man/man5/sudoers.5.gz
Post by Helmut Waitzmann
and send me the named files?
Could you please tell my email and I can send them to you.
Maybe the changelogs in “/usr/share/doc/sudo/” might tell anything
about tty or terminal?

Here you are:

Helmut Waitzmann Anti-Spam-Ticket.b.qc3c <***@xoxy.net>

My reply-to address is valid, that is, you may just reply by e-mail to
this posting.
Hongyi Zhao
2017-03-18 22:28:39 UTC
Permalink
Maybe the changelogs in “/usr/share/doc/sudo/” might tell anything about
tty or terminal?
My reply-to address is valid, that is, you may just reply by e-mail to
this posting.
Done, see them in attachments.

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-03-27 16:19:02 UTC
Permalink
Post by Hongyi Zhao
ls
EOF
stdin: is not a tty
$
Why?
Again, it might be better to give the shell a command, that won't
output nothing by accident (like “ls”), for example

“date”, “id”, or “pwd”.

Also, to show the exit code might help: After the line solely consisting
of the string “EOF” you could type the command

“printf 'exit code: %s\n' "$?"”

to let the shell show the exit code of the preceding sudo command.

What could be the cause of the message “stdin: is not a tty”, I
don't know, but there are some guesses:

Sudo might do option processing until it reaches the “-s” shell
option. To be sure that that isn't the case, the sudo option “--”
could be used (in this case: following the “-i” sudo option) to
explicitly stop option processing.

Maybe the login shell's startup files (for example
“"$HOME"/.profile”) might run some command that requests that
standard input be a terminal? (Which the startup files are,
depends on what shell root's login shell is.)

$ PS2='? '
$ sudo -u root -i -- sh -c 'exec "$SHELL" ${1+"$@"}' sh -s <<\EOF
? pwd
? EOF
$ printf 'exit code: %s\n' "$?"
Hongyi Zhao
2017-03-30 03:26:02 UTC
Permalink
Post by Helmut Waitzmann
$ PS2='? '
? pwd
? EOF
$ printf 'exit code: %s\n' "$?"
See the following for my case:

***@debian-01:~$ PS2='? '
***@debian-01:~$ sudo -u root -i -- sh -c 'exec "$SHELL" ${1+"$@"}' sh
-s <<\EOF
? pwd
? EOF
stdin: is not a tty
/root
***@debian-01:~$ printf 'exit code: %s\n' "$?"
exit code: 0
***@debian-01:~$

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Barry Margolin
2017-03-30 15:07:52 UTC
Permalink
Post by Helmut Waitzmann
Post by Helmut Waitzmann
$ PS2='? '
? pwd
? EOF
$ printf 'exit code: %s\n' "$?"
-s <<\EOF
? pwd
? EOF
stdin: is not a tty
/root
exit code: 0
Regards
I suspect there's something in root's .profile that executes a command
that requires stdin to be a terminal.

Put "set -x" at the beginning of the profile so you see what command is
executed right before the error message.
--
Barry Margolin, ***@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
Hongyi Zhao
2017-03-31 00:13:04 UTC
Permalink
Post by Barry Margolin
I suspect there's something in root's .profile that executes a command
that requires stdin to be a terminal.
Put "set -x" at the beginning of the profile so you see what command is
executed right before the error message.
See the following output based on your above notes:

***@debian-01:~$ PS2='? '
***@debian-01:~$ sudo -u root -i -- sh -c 'exec "$SHELL" ${1+"$@"}' sh
-s <<\EOF
? pwd
? EOF
+ '[' /bin/bash ']'
+ '[' -f /root/.bashrc ']'
+ . /root/.bashrc
+ mesg n
stdin: is not a tty
+ sh -c 'exec "/bin/bash" ${1+"$@"}' sh -s
/root
***@debian-01:~$

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Lew Pitcher
2017-03-31 00:45:42 UTC
Permalink
Post by Helmut Waitzmann
Post by Barry Margolin
I suspect there's something in root's .profile that executes a command
that requires stdin to be a terminal.
Put "set -x" at the beginning of the profile so you see what command is
executed right before the error message.
-s <<\EOF
? pwd
? EOF
+ '[' /bin/bash ']'
+ '[' -f /root/.bashrc ']'
+ . /root/.bashrc
+ mesg n
stdin: is not a tty
Bingo!

mesg(1) reads:

NAME
mesg - display (do not display) messages from other users

SYNOPSIS
mesg [ n ] [ y ]

DESCRIPTION
The mesg utility is invoked by a users to control write access
others have to the terminal device associated with the standard error
output. If write access is allowed, then programs such as talk(1)
and write(1) may display messages on the terminal.

So, in the /root/.bashrc script, you will find an invocation of the mesg(1)
utility. Because stdin to the root shell is not connected to a tty device,
mesg(1) fails (it needs a tty device as stdin).

Your root user likely does not need the mesg(1) utility; in modern Linux
systems, the root user is not often (if ever) logged in via a terminal
device. With discretion, you should be able to comment out or otherwise
disable this line in /root/.bashrc, without adverse effects.
Post by Helmut Waitzmann
/root
Regards
--
Lew Pitcher
"In Skills, We Trust"
PGP public key available upon request
Barry Margolin
2017-03-31 01:32:51 UTC
Permalink
Post by Lew Pitcher
Post by Helmut Waitzmann
Post by Barry Margolin
I suspect there's something in root's .profile that executes a command
that requires stdin to be a terminal.
Put "set -x" at the beginning of the profile so you see what command is
executed right before the error message.
-s <<\EOF
? pwd
? EOF
+ '[' /bin/bash ']'
+ '[' -f /root/.bashrc ']'
+ . /root/.bashrc
+ mesg n
stdin: is not a tty
Bingo!
NAME
mesg - display (do not display) messages from other users
SYNOPSIS
mesg [ n ] [ y ]
DESCRIPTION
The mesg utility is invoked by a users to control write access
others have to the terminal device associated with the standard error
output. If write access is allowed, then programs such as talk(1)
and write(1) may display messages on the terminal.
So, in the /root/.bashrc script, you will find an invocation of the mesg(1)
utility. Because stdin to the root shell is not connected to a tty device,
mesg(1) fails (it needs a tty device as stdin).
Your root user likely does not need the mesg(1) utility; in modern Linux
systems, the root user is not often (if ever) logged in via a terminal
device. With discretion, you should be able to comment out or otherwise
disable this line in /root/.bashrc, without adverse effects.
But if you really want it, just protect it with a check of stdin:

if [ -t 0 ]
then mesg n
fi

Or simply suppress the error message:

mesg n 2>/dev/null
--
Barry Margolin, ***@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
Hongyi Zhao
2017-03-31 23:07:14 UTC
Permalink
Post by Lew Pitcher
So, in the /root/.bashrc script, you will find an invocation of the mesg(1)
utility. Because stdin to the root shell is not connected to a tty
device, mesg(1) fails (it needs a tty device as stdin).
I inspected my /root/.bashrc script, but all stuff in this script is
commented out. So nothing is invocated, let alone mesg(1) utility, from
this script. See the following for detail:


***@debian-01:~# cat /root/.bashrc
# ~/.bashrc: executed by bash(1) for non-login shells.

# Note: PS1 and umask are already set in /etc/profile. You should not
# need this unless you want different defaults for root.
# PS1='${debian_chroot:+($debian_chroot)}\h:\w\$ '
# umask 022

# You may uncomment the following lines if you want `ls' to be colorized:
# export LS_OPTIONS='--color=auto'
# eval "`dircolors`"
# alias ls='ls $LS_OPTIONS'
# alias ll='ls $LS_OPTIONS -l'
# alias l='ls $LS_OPTIONS -lA'
#
# Some more alias to avoid making mistakes:
# alias rm='rm -i'
# alias cp='cp -i'
# alias mv='mv -i'
***@debian-01:~#

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Barry Margolin
2017-04-02 18:54:41 UTC
Permalink
Post by Hongyi Zhao
Post by Lew Pitcher
So, in the /root/.bashrc script, you will find an invocation of the mesg(1)
utility. Because stdin to the root shell is not connected to a tty
device, mesg(1) fails (it needs a tty device as stdin).
I inspected my /root/.bashrc script, but all stuff in this script is
commented out. So nothing is invocated, let alone mesg(1) utility, from
Check /root/.profile
Post by Hongyi Zhao
# ~/.bashrc: executed by bash(1) for non-login shells.
# Note: PS1 and umask are already set in /etc/profile. You should not
# need this unless you want different defaults for root.
# PS1='${debian_chroot:+($debian_chroot)}\h:\w\$ '
# umask 022
# export LS_OPTIONS='--color=auto'
# eval "`dircolors`"
# alias ls='ls $LS_OPTIONS'
# alias ll='ls $LS_OPTIONS -l'
# alias l='ls $LS_OPTIONS -lA'
#
# alias rm='rm -i'
# alias cp='cp -i'
# alias mv='mv -i'
Regards
--
Barry Margolin, ***@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
Hongyi Zhao
2017-04-04 07:05:35 UTC
Permalink
Post by Barry Margolin
Check /root/.profile
Good idea, this file is as follows:

---------------------
***@debian-01:~$ sudo cat /root/.profile
# ~/.profile: executed by Bourne-compatible login shells.


if [ "$BASH" ]; then
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
fi

mesg n
-----------------------

As you can see, it does have a invocation on mesg.

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Barry Margolin
2017-04-04 17:59:06 UTC
Permalink
Post by Hongyi Zhao
Post by Barry Margolin
Check /root/.profile
---------------------
# ~/.profile: executed by Bourne-compatible login shells.
if [ "$BASH" ]; then
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
fi
mesg n
-----------------------
As you can see, it does have a invocation on mesg.
Regards
It's not uncommon for .profile to assume that it's running in an
interactive terminal. It's only run in login shells, and you have to go
out of your way to make a non-interactive login shell.
--
Barry Margolin, ***@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
Hongyi Zhao
2017-04-04 23:04:11 UTC
Permalink
Post by Barry Margolin
It's not uncommon for .profile to assume that it's running in an
interactive terminal. It's only run in login shells,
All these are the the default settings shipped with Debian Jessie, and
you can see the info about the os:

***@debian-01:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.7 (jessie)
Release: 8.7
Codename: jessie
Post by Barry Margolin
and you have to go
out of your way to make a non-interactive login shell.
You mean put the code into .bashrc?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Barry Margolin
2017-04-05 15:59:20 UTC
Permalink
Post by Hongyi Zhao
Post by Barry Margolin
It's not uncommon for .profile to assume that it's running in an
interactive terminal. It's only run in login shells,
All these are the the default settings shipped with Debian Jessie, and
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.7 (jessie)
Release: 8.7
Codename: jessie
Post by Barry Margolin
and you have to go
out of your way to make a non-interactive login shell.
You mean put the code into .bashrc?
Regards
No, I mean redirecting the input to "su -l".

Actually, I went back to the OP, and I'm not sure why .profile was run
in the first place. The command was

su root <<EOF
...
EOF

That shouldn't create a login shell, so it should only run .bashrc, not
.profile.
--
Barry Margolin, ***@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
Hongyi Zhao
2017-04-08 00:46:14 UTC
Permalink
su root <<EOF ...
EOF
This method will not work just as the things previously discussed in this
thread. It will throw the error like this:

***@debian-01:~$ PS2='? '
***@debian-01:~$ su root <<EOF
? ls
? EOF
su: must be run from a terminal
***@debian-01:~$

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-04-05 01:43:52 UTC
Permalink
Post by Hongyi Zhao
Post by Barry Margolin
Check /root/.profile
---------------------
# ~/.profile: executed by Bourne-compatible login shells.
if [ "$BASH" ]; then
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
fi
mesg n
-----------------------
As you can see, it does have a invocation on mesg.
You might restrict the invocation of “mesg n” to an interactive
non-su login shell, the standard input of which is associated with
a terminal:

if
# Is this an interactive shell?
test " $-" != " ${-##*i*}" &&
# Does the shell's invocation basename differ from 'su' and
# '-su'?
( invoc_basename="${0##*/}" &&
test " ${invoc_basename#-}" != ' su'
) &&
# Is standard input associated with a terminal?
test -t 0
then
mesg n
fi
Helmut Waitzmann
2017-03-31 07:07:19 UTC
Permalink
Post by Helmut Waitzmann
Post by Barry Margolin
I suspect there's something in root's .profile that executes a
command that requires stdin to be a terminal.
Put "set -x" at the beginning of the profile so you see what
command is executed right before the error message.
-s <<\EOF
? pwd
? EOF
+ '[' /bin/bash ']'
+ '[' -f /root/.bashrc ']'
+ . /root/.bashrc
+ mesg n
stdin: is not a tty
/root
As you can see, “mesg” is the culprit: Root's shell, as specified
in the user database, is a “/bin/bash”, which, when started as a
login shell, reads and executes its startup files. In one of them
the “mesg” command is eventually started, which requests that
standard input be connected to a terminal.
Hongyi Zhao
2017-08-02 23:33:55 UTC
Permalink
Post by Helmut Waitzmann
The command
-s 3<<EOF
would roughly correspond to the command
-s <<\EOF
I tried all of the following forms, they all give the same results:

[1] Your original form given above:

$ sudo -u root -i sh -c 'exec "$SHELL" ${1+"$@"}' sh -s <<\EOF
echo $HOME $USER
echo $PATH
EOF
/root root
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin


[2] Without using the “-s” option:

$ sudo -u root -i sh -c 'exec "$SHELL" ${1+"$@"}' sh <<\EOFecho $HOME
$USER
echo $PATH
EOF
/root root
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

[3] Furthermore, without using “sh -c 'exec "$SHELL" ${1+"$@"}'”:

$ sudo -u root -i sh <<\EOF
echo $HOME $USER
echo $PATH
EOF
/root root
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

[4] Furthermore, without using “-u root -i”:

$ sudo sh <<\EOF
echo $HOME $USER
echo $PATH
EOF
/root root
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

As you can see, all of the above forms on my Debian stretch box will give
the same results when I test on these basic and import variables.

So, it should be equivalent when I give the same heredoc for running
commands using any of the above forms.

If so, what's the differences among them?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-08-04 13:16:36 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
The command
-s 3<<EOF
would roughly correspond to the command
-s <<\EOF
echo $HOME $USER
echo $PATH
EOF
/root root
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
echo $HOME $USER
echo $PATH
EOF
/root root
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
In your example, both of the commands are equivalent: The “-s”
option is only necessary, if one wants to supply operands. But it
does no harm, when no operands are supplied.

See the manual page of the shell or the section
<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html#tag_20_117_04>
in the specification of the POSIX Shell
<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html#top>:

-s
Read commands from the standard input.

If there are no operands and the -c option is not specified, the
-s option shall be assumed.
Post by Hongyi Zhao
$ sudo -u root -i sh <<\EOF
echo $HOME $USER
echo $PATH
EOF
/root root
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
In example [2], a shell, as specified by the shell entry of the
user database for user “root”, eventually will be invoked. To
find out, what shell the user database specifies, run the command


sed -e '/^root:/!d' \
-e 's/^\([^:]*:\)\{5\}[^:]*:\{0,1\}//' \
-e '/^$/s//\/bin\/sh/' -- /etc/passwd
”.

(On my system, I get “/bin/bash”.)

In example [3], eventually a “sh” (rather than a “"$SHELL"”) for
user “root” will be invoked.
Post by Hongyi Zhao
$ sudo sh <<\EOF
echo $HOME $USER
echo $PATH
EOF
/root root
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
There is a “sudo” configuration option, “runas_default” (see the
“sudoers(5)” manual page), which specifies, what user the command
is to be run as, if one doesn't specify the “sudo” option “-u”
together with its parameter.

As I don't know, wheather the “sudo” configuration at your system
specifies a different run-as default user than “root”, I
explicitly gave the parameters “-u root”.

If the “-i” sudo option is not supplied, the shell invoked by
“sudo” (i. e. the target user's shell) will be a non-login shell.
The manual page of the target user's shell should tell, what it
means to the shell to be or not to be invoked as a login shell.

If the shell invoked by “sudo” is not a login shell, then –
depending on, what the system administrator configured for the
pluggable authentication modules (see the “pam(7)” manual page),
“sudo”, and the target user's shell, and, what the target user
configured for his shell – the invoked shell may run in a possibly
poorly set up process environment, which may work or may not
work for you.
Post by Hongyi Zhao
As you can see, all of the above forms on my Debian stretch box
will give the same results when I test on these basic and import
variables.
So, it should be equivalent when I give the same heredoc for
running commands using any of the above forms.
You might repeat your tests with the commands

(
if test -n "${0+defined}"
then
fmt=': %q'
else
fmt=' %s'
fi
bash -c '"$@"' bash printf \
'Invocation name of the shell'"${fmt}"'\n' \
"${0-is undefined}"
)
set -x
umask
pwd
env

in the here-document.

Regards
Hongyi Zhao
2017-08-06 02:25:51 UTC
Permalink
Post by Helmut Waitzmann
In your example, both of the commands are equivalent: The “-s”
option is only necessary, if one wants to supply operands. But it does
no harm, when no operands are supplied.
See the manual page of the shell or the section
<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/
sh.html#tag_20_117_04>
Post by Helmut Waitzmann
in the specification of the POSIX Shell
-s
Read commands from the standard input.
If there are no operands and the -c option is not specified, the -s
option shall be assumed.
This is for `sh' utility, on my Debian os, the ``man sh'' is for dash:


$ man sh

DASH(1) BSD General Commands Manual
DASH(1)

NAME
dash — command interpreter (shell)


And I can find the following explanations on ``-s'':

-s stdin Read commands from standard input (set automati‐
cally if no file arguments are present). This
option has no effect when set after the shell has
already started running (i.e. with set).

It seems this description is not completely compatible with opengroup's
specification on ``sh''.

On the other hand, in fact, I rarely use dash as my shell interpretor, I
often use bash. And bash's manpage says the following for ``-s'':


-s If the -s option is present, or if no arguments remain after
option processing, then commands are read from the standard
input. This option allows the positional parameters to be
set when invoking an interactive shell.

Seems this description is more agreeable with the behavior of opengroup's
specification on ``sh''.


Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Hongyi Zhao
2017-08-06 02:46:57 UTC
Permalink
In example [2], a shell, as specified by the shell entry of the user
database for user “root”, eventually will be invoked. To find out, what
shell the user database specifies, run the command

sed -e '/^root:/!d' \
-e 's/^\([^:]*:\)\{5\}[^:]*:\{0,1\}//' \
-e '/^$/s//\/bin\/sh/' -- /etc/passwd
By the last sed command used above, do you mean, when not set, the ``/bin/
sh'' will be used as the default shell for that user?
”.
Why not just using awk for this purpose:

$ awk 'BEGIN {FS=":" } /^root:/{ print $NF }' /etc/passwd
/bin/bash
$
(On my system, I get “/bin/bash”.)
Same to me, see the above output of awk's command for confirmation.
In example [3], eventually a “sh” (rather than a “"$SHELL"”) for user
“root” will be invoked.
Thanks for your explanation.

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Hongyi Zhao
2017-06-12 13:32:16 UTC
Permalink
Post by Helmut Waitzmann
su 3<<EOF
Sorry for long later reply on this thread again.

In fact, I've tried with the file descriptor larger than 2, say, 3, 4,
5, ..., and all of them works just as the above code. So, why you
specifically use file descriptor #3 here?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Barry Margolin
2017-06-12 15:27:41 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
su 3<<EOF
Sorry for long later reply on this thread again.
In fact, I've tried with the file descriptor larger than 2, say, 3, 4,
5, ..., and all of them works just as the above code. So, why you
specifically use file descriptor #3 here?
Regards
He has to pick something, 3 is the first available FD.
--
Barry Margolin, ***@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
Helmut Waitzmann
2017-06-13 05:06:57 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
su 3<<EOF
Sorry for long later reply on this thread again.
In fact, I've tried with the file descriptor larger than 2, say, 3, 4,
5, ..., and all of them works just as the above code.
Yes, you are right.
Post by Hongyi Zhao
So, why you specifically use file descriptor #3 here?
I could have used any unused file descriptor. There is nothing
special with #3 here. It's just the smallest available (#0, #1,
and #2 are already in use).
Hongyi Zhao
2017-06-15 10:22:55 UTC
Permalink
Post by Helmut Waitzmann
su 3<<EOF
Another issue: by using the construction -c '...' in the above code, this
will disable the variables expansion. Why still the "$SHELL" and ${1
+"$@"} parts still can be expanded correctly and executed accordingly?

Furthermore, should I use

-c '...'

or

-c "..."

in the above code line given by you?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Barry Margolin
2017-06-15 14:23:03 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
su 3<<EOF
Another issue: by using the construction -c '...' in the above code, this
will disable the variables expansion. Why still the "$SHELL" and ${1
They're expanded by the new shell process that "su" runs.
Post by Hongyi Zhao
Furthermore, should I use
-c '...'
or
-c "..."
in the above code line given by you?
If you use single quotes, the variables are expanded by the new shell.
If you use double quotes, the variables are expanded by the original
shell.
--
Barry Margolin, ***@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
Kaz Kylheku
2017-06-15 15:40:30 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
su 3<<EOF
Another issue: by using the construction -c '...' in the above code, this
will disable the variables expansion.
It will disable any variable expansion when the whole -c '...'
argument is processed here, before "su" is dispatched.
Post by Hongyi Zhao
Why still the "$SHELL" and ${1
$ man su
[...]

OPTIONS
The options which apply to the su command are:

-c, --command COMMAND
Specify a command that will be invoked by the shell using its -c.

What part isn't clear? The syntax between the '...' in su -c '...' is
treated as a shell command.

Here is a C program:

#include <stdlib.h>
int main(int argc, char **argv)
{
system("echo $TERM");
return 0;
}

The C language compiler certainly doesn't expand shell variables in string
literals. Yet, the value of TERM gets printed. How come?

The su program is essentially the same as the above. It finds that some
argument argv[i] contains "-c" and so it does something resembling
system(argv[i+1]) to run the following one as a shell command.
Post by Hongyi Zhao
Furthermore, should I use
-c '...'
or
-c "..."
in the above code line given by you?
Strictly speaking, the code line given by Helmutt Weitzmann *doesn't*
use "...", and so you cannot.

If you make that substitution, it is no longer the line given by Helmutt
Weitzmann.
Helmut Waitzmann
2017-06-15 21:43:01 UTC
Permalink
Post by Kaz Kylheku
Post by Hongyi Zhao
Post by Helmut Waitzmann
su 3<<EOF
Another issue: by using the construction -c '...' in the above code, this
will disable the variables expansion.
It will disable any variable expansion when the whole -c '...'
argument is processed here, before "su" is dispatched.
Post by Hongyi Zhao
Why still the "$SHELL" and ${1
$ man su
[...]
OPTIONS
-c, --command COMMAND
Specify a command that will be invoked by the shell using its -c.
What part isn't clear? The syntax between the '...' in su -c '...' is
treated as a shell command.
#include <stdlib.h>
int main(int argc, char **argv)
{
system("echo $TERM");
return 0;
}
The C language compiler certainly doesn't expand shell variables in string
literals. Yet, the value of TERM gets printed. How come?
The su program is essentially the same as the above. It finds that some
argument argv[i] contains "-c" and so it does something resembling
system(argv[i+1]) to run the following one as a shell command.
Not exactly. “su” will use the “execl()” library function (or a
variant of it) rather than the “system()” library function.

When “su” parses its arguments given by the shell
command line

“su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' \
su 3<<EOF”,

it encounters the first argument, “--”, which tells it to stop
option processing, that is, all remaining arguments are meant to
not be “su” options, even if they start with a dash (“-”).

In the next argument (which in this case is the second one), it
expects either a single dash (“-”) to indicate, that the shell to
be started should be a login shell, or a user name for the target
user (which is the case here: “root”).

(If this argument were “-” rather than “root”, a following
additional argument would be the target user's name.)

Then “su” looks up the target user's shell in the user database,
which, for example, be “/bin/bash”.

“su” asks for the password of the target user, sets up a (basic)
process environment, changes its process credentials to the target
user, and eventually executes the target user's shell by means of
a call to the “execl()” system library function.

To call that function, “su” constructs that function's argument
list:

* The first argument will be the path name of root's shell, for
example, “/bin/bash”.

* The second argument will be (in this case) the base name of the
first argument: “bash”.

“su” copies all remaining arguments as arguments to the system
library function call, as would be the case, if you wrote a C
program containing the following function call. So it won't
bother to interpret its third argument (“-c”) to be the su option
“-c”:

execl(
"/bin/bash", "bash", "-c",
"exec 0<&3 3<&- && \"$SHELL\" ${1+\"$@\"}",
"su", (const char *) NULL
)

(Note: The “\"” sequences are C's notation for quotation marks in
strings.)

That function eventually will execute a “/bin/bash“, giving it the
invocation name “bash” and supplying the arguments

“-c”,
“exec 0<&3 3<&- && "$SHELL" ${1+"$@"}”, and
“su”.

The same would happen, if you typed the command line

“/bin/bash -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su”,

which of cause is very similar to the original command line,
above.
Post by Kaz Kylheku
Post by Hongyi Zhao
Furthermore, should I use
-c '...'
or
-c "..."
in the above code line given by you?
If you used

“su -- root -c "exec 0<&3 3<&- && "$SHELL" ${1+"$@"}" \
su 3<<EOF”

you would get a totally different, wrong, argument list for “su”,
for “execl()“, and eventually for the target user's shell, because
the part

“"exec 0<&3 3<&- && "$SHELL" ${1+"$@"}"”

of the command line would be mangled by the invoking shell: It
would construct that part of the command line for “su” as follows:

A first word of this part of the command line would be the
concatenation of the following strings, split by the characters of
the “IFS” shell variable in the value of the “SHELL” shell
variable:

* “exec 0<&3 3<&- && ”,

* the contents of the shell variable “SHELL”, for example
“/bin/zsh“ (if you happen to have “/bin/zsh” as the contents of
your shell variable “SHELL”),

* “ ”, and

* the first positional parameter of your invoking shell, if any.

Any remaining positional parameters of your invoking shell would
make up a second, third, … word of this part of the command line.

If, for example, there were no positional parameters in your
interactive shell, and the contents of the “SHELL” shell variable
were “/bin/zsh”, the command line would be equivalent to the
command line

“su -- root -c 'exec 0<&3 3<&- && /bin/zsh ' \
su 3<<EOF”.

As a rule of thumb:

If you want to pass an arbitrary string as part of a command line
as an invocation argument to a command, enclose that string in
apostrophes (“'”) rather than in quotation marks (“"”), for
example

“'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}'”.

If you want to pass the contents of a shell variable as part of a
command line as an invocation argument to a command, enclose that
variable expansion in quotation marks (“"”), for example

“"$SHELL"”.
Hongyi Zhao
2017-06-16 01:19:30 UTC
Permalink
Post by Helmut Waitzmann
The same would happen, if you typed the command line
which of cause is very similar to the original command line, above.
But, I tried the following:

$ /bin/bash -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su 3<<EOF
ls
EOF

This will not need to supply the password for running su. Why?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-06-16 02:19:15 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
The same would happen, if you typed the command line
which of cause is very similar to the original command line, above.
ls
EOF
This will not need to supply the password for running su. Why?
Of cause, not. The command line

“/bin/bash -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su”

describes, what “su” does, /after/ it has verified the password,
set up a process environment and changed the process credentials
to “root”: It calls the “execl()” system library function like a
shell, when you give it the command line named above, does.
Helmut Waitzmann
2017-06-16 03:33:00 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
The same would happen, if you typed the command line
which of cause is very similar to the original command line, above.
ls
EOF
This will not need to supply the password for running su. Why?
It will not run “su”. It will just run a “/bin/bash”, giving it
the invocation arguments
“bash”,
“-c”,
“exec 0<&3 3<&- && "$SHELL" ${1+"$@"}”, and
“su”.

The last argument, “su”, will be assigned to the positional
parameter “$0”.

Look at the bash(1) manual page:

OPTIONS
In addition to the single-character shell options
documented in the description of the set builtin command,
bash interprets the following options when it is invoked:

-c string If the -c option is present, then commands are
read from string. If there are arguments after
the string, they are assigned to the positional
parameters, starting with $0.

In the example above, the bash's positional parameter $0 will be
“su”.
Helmut Waitzmann
2017-06-16 04:21:57 UTC
Permalink
Post by Helmut Waitzmann
Post by Hongyi Zhao
Post by Helmut Waitzmann
The same would happen, if you typed the command line
which of cause is very similar to the original command line, above.
ls
EOF
This will not need to supply the password for running su. Why?
It will not run “su”. It will just run a “/bin/bash”, giving it
the invocation arguments
“bash”,
“-c”,
“su”.
If you give the “id” rather than the “ls” command, you can see
that. Try:

$ /bin/bash -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su 3<<EOF
id
EOF
Hongyi Zhao
2017-06-16 12:24:26 UTC
Permalink
If you give the “id” rather than the “ls” command, you can see that.
id
EOF
I tried with and without using ``su'' in the above code, and both of them
gives the same results:

$ /bin/bash -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su 3<<EOF
id
EOF
uid=1000(werner) gid=1000(werner) groups=1000(werner),24(cdrom),25
(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev),111
(scanner),115(bluetooth)

$ /bin/bash -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' 3<<EOF
id
EOF
uid=1000(werner) gid=1000(werner) groups=1000(werner),24(cdrom),25
(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev),111
(scanner),115(bluetooth)

So, why must you use ``su'' here?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-06-16 17:11:52 UTC
Permalink
Post by Hongyi Zhao
If you give the “id” rather than the “ls” command, you can see that.
id
EOF
I tried with and without using ``su'' in the above code, and both of them
id
EOF
uid=1000(werner) gid=1000(werner) groups=1000(werner),24(cdrom),25
(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev),111
(scanner),115(bluetooth)
id
EOF
uid=1000(werner) gid=1000(werner) groups=1000(werner),24(cdrom),25
(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev),111
(scanner),115(bluetooth)
So, why must you use ``su'' here?
As long as you don't supply additional arguments and and you are
not going to call a login shell, in your example might be no
difference between supplying that “su” argument and not supplying
it. (Whether actually there /is/ a difference anyway, depends on
the shell to be called and on its startup files.)

But if you intend to supply any additional arguments, that
argument will be mandatory.

Look at the “bash(1)” manual page in the section “OPTIONS”:

In addition to the single-character shell options
documented in the description of the set builtin command,
bash interprets the following options when it is invoked:

-c string If the -c option is present, then commands are
read from string. If there are arguments after
the string, they are assigned to the positional
parameters, starting with $0.

In your example, the “-c” option is present and “su” will
be assigned to the positional parameter “"$0"”. Additional
arguments will be assigned to the positional parameters “"$@"”.

See also your posting <news:oa84ej$cjn$***@dont-email.me> (where you
asked, why I supplied two “su” commands) and my followup
<news:***@helmutwaitzmann.news.arcor.de>:

From: Helmut Waitzmann <***@xoxy.net>
Newsgroups: comp.unix.shell
Subject: Re: su: must be run from a terminal
Reply-To: Helmut Waitzmann Anti-Spam-Ticket.b.qc3c
<***@xoxy.net>
Mail-Reply-To: Helmut Waitzmann Anti-Spam-Ticket.b.qc3c
<***@xoxy.net>
Mail-Copies-To: nobody
Date: Wed, 15 Mar 2017 16:48:38 +0100
Message-ID: <***@helmutwaitzmann.news.arcor.de>
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/23.2 (gnu/linux)
References: <oa68hg$p7t$***@dont-email.me>
<***@helmutwaitzmann.news.arcor.de>
<oa84ej$cjn$***@dont-email.me>
Cancel-Lock: sha1:7rP9O7GaEGoz3FB9i3SnxNCQ49Y=
MIME-Version: 1.0

See also your posting <news:oadb4g$o64$***@aspen.stu.neva.ru>, where
you asked, if it were possible to pass shell variables to a
here-document. In my followup, I suggested, to pass them by means
of additional arguments and to use a while loop to let the shell
process its positional parameters to retrieve the variables:

Subject: Re: su: must be run from a terminal
From: Helmut Waitzmann <***@xoxy.net>
Reply-To: Helmut Waitzmann Anti-Spam-Ticket.b.qc3c
<***@xoxy.net>
Mail-Reply-To: Helmut Waitzmann Anti-Spam-Ticket.b.qc3c
<***@xoxy.net>
Mail-Copies-To: nobody
Newsgroups: comp.unix.shell
References: <oa68hg$p7t$***@dont-email.me>
<***@helmutwaitzmann.news.arcor.de>
<oadb4g$o64$***@aspen.stu.neva.ru>
<***@helmutwaitzmann.news.arcor.de>
<ohnacv$1h4$***@aspen.stu.neva.ru>
Date: Tue, 13 Jun 2017 07:42:09 +0200
Message-ID: <***@helmutwaitzmann.news.arcor.de>
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/23.2 (gnu/linux)
MIME-Version: 1.0

If you want to do that, then the “su” argument is mandatory:

For example:

su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su \
-s -- dir="$HOME" -- */ \
3<<\EOF
while test "$#" -ge 1
do
if test " $1" = ' --'
then
# end of variable list: stop processing variables
shift
break
elif LC_ALL=C \
expr " $1" : ' [[:alpha:]_][[:alnum:]_]*=' > /dev/null
then
if eval "${1%%=*}="'"${1#*=}"'
then
shift
else
printf '%s:\nThe variable assignment\n%s\nfailed.\n' \
"$0" "$1" >&2
return 1
fi
else
printf '%s:\n%s\nis not a variable assignment.\n' \
"$0" "$1" >&2
return 1
fi
done
printf '%s\n' "$HOME" "$dir" "$@"
EOF
Hongyi Zhao
2017-06-21 08:59:59 UTC
Permalink
On Fri, 16 Jun 2017 19:11:52 +0200, Helmut Waitzmann
Post by Helmut Waitzmann
-s -- dir="$HOME" -- */ \
3<<\EOF
while test "$#" -ge 1
do
if test " $1" = ' --'
then
# end of variable list: stop processing variables
shift
break
I think for some time on the `break' used here, but still now so
clear about the logic here. In detail:

`break' will let the code exit the while loop. So, how the while loop
can still do the subsequent parameters analysis?
Post by Helmut Waitzmann
elif LC_ALL=C \
Why you use LC_ALL=C here? I noted that you don't use this line in
the previous version of the code for similar purpose.
Post by Helmut Waitzmann
expr " $1" : ' [[:alpha:]_][[:alnum:]_]*=' > /dev/null
then
if eval "${1%%=*}="'"${1#*=}"'
Is this needed really? I mean, when will the `eval' failed to do the
job?
Post by Helmut Waitzmann
then
shift
else
printf '%s:\nThe variable assignment\n%s\nfailed.\n' \
"$0" "$1" >&2
return 1
fi
else
printf '%s:\n%s\nis not a variable assignment.\n' \
"$0" "$1" >&2
return 1
fi
done
EOF
Regards
Helmut Waitzmann
2017-06-22 00:25:43 UTC
Permalink
Post by Hongyi Zhao
On Fri, 16 Jun 2017 19:11:52 +0200, Helmut Waitzmann
Post by Helmut Waitzmann
-s -- dir="$HOME" -- */ \
3<<\EOF
while test "$#" -ge 1
do
if test " $1" = ' --'
then
# end of variable list: stop processing variables
shift
break
I think for some time on the `break' used here, but still now so
`break' will let the code exit the while loop.
Yes. That's on purpose.
Post by Hongyi Zhao
So, how the while loop can still do the subsequent parameters
analysis?
As soon as the while loop encounters a “--” (two dashes) argument
(“test " $1" = ' --'”), it removes that argument from the argument
list (“shift”) and then stops the subsequent arguments' analysis,
letting the subsequent arguments remain as plain positional
parameters rather than variable assignments.

This allows you to pass additional arguments, that look like
variable assignments, but are meant to be plain positional
parameters, to the command.

Therefore you can call it with a (possibly empty) list of variable
assignments followed by “--” and an (possibly empty) list of
additional positional parameters, as was the case with the example
(see the first three lines of the example):

su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su \
-s -- dir="$HOME" -- */ \
3<<\EOF

“dir="$HOME"” is one argument meant to be a variable assignment,
“--” stops the assignment processing, and “*/” are additional
positional parameters. Imagine, what would happen, if you'd
happen to have a subdirectory called “hello=world” and no
assignment stopper (“--”).
Post by Hongyi Zhao
Post by Helmut Waitzmann
elif LC_ALL=C \
Why you use LC_ALL=C here? I noted that you don't use this line in
the previous version of the code for similar purpose.
“LC_ALL=C” ensures, that the character class “[:alpha:]” (as
“expr” understands it) consists solely of the 26 uppercase and 26
lowercase letters of the latin alphabet, and, that the character
class “[:alnum:]” consists of the 26 uppercase and 26 lowercase
letters of the latin alphabet plus the digits 0 to 9, regardless
of what the default locale is. In the POSIX shell, variable names
are made up of a leading letter of the latin alphabet or an
underscore, followed up by zero or more letters of the latin
alphabet, underscores or digits. This is, what the commandline

“LC_ALL=C expr " $1" : ' [[:alpha:]_][[:alnum:]_]*=' > /dev/null”

tests for.
Post by Hongyi Zhao
Post by Helmut Waitzmann
expr " $1" : ' [[:alpha:]_][[:alnum:]_]*=' > /dev/null
then
if eval "${1%%=*}="'"${1#*=}"'
Is this needed really? I mean, when will the `eval' failed to do the
job?
For example, may be, that some shells have a restriction, in how long a
variable name is allowed to be (I don't know). I am just careful
here, as I don't want to risk the command to be run with incomplete or
wrong variable assignments made.
Hongyi Zhao
2017-06-22 01:47:07 UTC
Permalink
As soon as the while loop encounters a “--” (two dashes) argument (“test
" $1" = ' --'”),
In your previous ``case-command-based'' version, you do the test as
follows:

case "$1" in
--)

It seems the case command will more concise for the same purpose, why
here you changed to use test command for this purpose?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-06-22 15:26:48 UTC
Permalink
Post by Hongyi Zhao
As soon as the while loop encounters a “--” (two dashes) argument (“test
" $1" = ' --'”),
In your previous ``case-command-based'' version, you do the test as
case "$1" in
--)
It seems the case command will more concise for the same purpose, why
here you changed to use test command for this purpose?
With the “case” construct, all tests are pattern matching tests.
If I want to mix different sorts of tests: “test” and “expr”, I've
to resort on the “if … elif” construct and can no longer
use the “case” construct.
Hongyi Zhao
2017-06-22 02:08:15 UTC
Permalink
Post by Helmut Waitzmann
Therefore you can call it with a (possibly empty) list of variable
assignments followed by “--” and an (possibly empty) list of additional
positional parameters, as was the case with the example (see the first
-s -- dir="$HOME" -- */ \
3<<\EOF
“dir="$HOME"” is one argument meant to be a variable assignment, “--”
stops the assignment processing, and “*/” are additional positional
parameters. Imagine, what would happen, if you'd happen to have a
subdirectory called “hello=world” and no assignment stopper (“--”).
But the previous version you told me is as follows:

su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su \
-s aa="$aa" bb='some value' -- some positional parameters \
3<<\EOF

This version only has one “--” here, i.e., the following part in the
above code:

aa="$aa" bb='some value' -- some positional parameters


But in your latter version, you use tow “--” for the same purpose, i.e.,
the following part in the latter version of your code:

-- dir="$HOME" -- */

If one “--” is enough for the same purpose, why you will use two “--”
in the latter version?

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-06-23 11:45:45 UTC
Permalink
Post by Helmut Waitzmann
Post by Helmut Waitzmann
Therefore you can call it with a (possibly empty) list of variable
assignments followed by “--” and an (possibly empty) list of additional
positional parameters, as was the case with the example (see the first
-s -- dir="$HOME" -- */ \
3<<\EOF
“dir="$HOME"” is one argument meant to be a variable assignment, “--”
stops the assignment processing, and “*/” are additional positional
parameters. Imagine, what would happen, if you'd happen to have a
subdirectory called “hello=world” and no assignment stopper (“--”).
-s aa="$aa" bb='some value' -- some positional parameters \
3<<\EOF
This version only has one “--” here, i.e., the following part in the
aa="$aa" bb='some value' -- some positional parameters
But in your latter version, you use tow “--” for the same purpose, i.e.,
-- dir="$HOME" -- */
If one “--” is enough for the same purpose, why you will use two “--”
in the latter version?
I'm sorry. I should have used the “--” following the shell option
“-s” in the first version as well. In both versions, it's
optional, because the first non-option parameter for the
“"$SHELL"” (“aa="$aa"” and “dir="$HOME"”, resp.) happens not to
start with a “-”, but in an example like

su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su \
-s -- -- no variable assignments, just additional arguments \
3<<\EOF

where the first non-option argument of the “"$SHELL"” starts with
a “-”, the “--” shell invocation option stopper is mandatory. So,
to get the correct behavior, regardless, whether the first
non-option parameter of the “"$SHELL"” start with a “-” or not, I
suggest always to put an option stopper (“--”) before it.

And there is an error in the here-document: The “return 1”
commands are wrong. They should be “exit 1” commands. (The
“return 1” commands only work, when the loop is put into a shell
script, which is executed by means of the “.” command.)

I'll try to explain, what's going on here. In the example

su -- root -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su \
-s -- -- no variable assignments, just additional arguments \
3<<\EOF
while test "$#" -ge 1
do
if test " $1" = ' --'
then
# end of variable list: stop processing variables
shift
break
elif LC_ALL=C \
expr " $1" : ' [[:alpha:]_][[:alnum:]_]*=' > /dev/null
then
if eval "${1%%=*}="'"${1#*=}"'
then
shift
else
printf '%s:\nThe variable assignment\n%s\nfailed.\n' \
"$0" "$1" >&2
exit 1
fi
else
printf '%s:\n%s\nis not a variable assignment.\n' \
"$0" "$1" >&2
exit 1
fi
done
printf '%s\n' 'These are the positional parameters:' "$@"
EOF

the invoking shell parses this command, which uses redirection
From a here-document.

The shell puts the text of the here-document in a temporary file,
then opens that file for reading and redirects the file descriptor
#3 to the opened file. Eventually it calls “su”, giving it the
arguments

“--”, “root”, “-c”, “exec 0<&3 3<&- && "$SHELL" ${1+"$@"}”,
“su”, “-s”, “--”, “--”, “no”, “variable”, “assignments,”, “just”,
“additional”, and “arguments”.

“su”, when parsing its arguments, encounters the “--” argument,
which tells “su” to stop option processing. The next argument
shall either be the target user name (which is the case here) or a
“-” followed by an additional argument: the target user name.
“su” eventually invokes root's shell (may it be “/bin/bash”),
passing it all remaining arguments, which in this example are

“-c”, “exec 0<&3 3<&- && "$SHELL" ${1+"$@"}”, “su”, “-s”, “--”,
“--”, “no”, “variable”, “assignments,”, “just”, “additional”, and
“arguments”, as if one had started it with the command line

/bin/bash -c 'exec 0<&3 3<&- && "$SHELL" ${1+"$@"}' su \
-s -- -- no variable assignments, just additional arguments

That bash parses its arguments: “-c” tells it to expect a command
line in the following argument and to assign all remaining
arguments to the shell special parameters: The first of them is
assigned to “"$0"” (in this example: “su”), the remaining ones are
assigned to “"$@"” (in this example: “-s”, “--”, “--”, “no”,
“variable”, “assignments,”, “just”, “additional”, and
“arguments”).

Now it executes its command line: It redirects file descriptor #0
to file descriptor #3 (which happens to be the here-document),
closes file descriptor #3, and starts (assuming the environment
variable “SHELL” has got the value “/bin/bash”) a “/bin/bash”,
giving it the arguments “-s”, “--”, “--”, “no”, “variable”,
“assignments,”, “just”, “additional”, and “arguments” , as if it
had started it by the command line

“/bin/bash -s -- -- no variable assignments, just additional \
arguments”.

That bash examines its arguments: At first it encounters the “-s”
option, that tells it, to read commands from its standard input
(which was redirected to the here-document). Then it encounters
the “--” following the “-s”. The “--” causes the shell to stop
option processing: It discards the “--” and lets the
following arguments

“--”, “no”, “variable”, “assignments,”, “just”, “additional”, and
“arguments”

move up and remain as positional parameters, even, if they start
with a “-”; that is, they are accessible via the shell's “"$@"”
and “"$1"”, “"$2"”, … special parameters:

“"$1"” expands to “--”,
“"$2"” expands to “no”,
“"$3"” expands to “variable”,
“"$4"” expands to “assignments,”,
“"$5"” expands to “just”,
“"$6"” expands to “additional”, and
“"$7"” expands to “arguments”.

Then the shell reads commands from its standard input. In this
example the commands, which are read from standard input (the
here-document), are

while test "$#" -ge 1
do
if test " $1" = ' --'
then
# end of variable list: stop processing variables
shift
break
elif LC_ALL=C \
expr " $1" : ' [[:alpha:]_][[:alnum:]_]*=' > /dev/null
then
if eval "${1%%=*}="'"${1#*=}"'
then
shift
else
printf '%s:\nThe variable assignment\n%s\nfailed.\n' \
"$0" "$1" >&2
exit 1
fi
else
printf '%s:\n%s\nis not a variable assignment.\n' \
"$0" "$1" >&2
exit 1
fi
done
printf '%s\n' 'These are the positional parameters:' "$@"

Interpreting the “while” loop, the shell calls the command

“test 7 -ge 1“,

which yields exit code 0, and enters the body of the loop. Then
it tests the condition of the first “if” compound command, that
is, it calls the “test” command, expanding “"$1"” by “--”, as if
given by the command line

“test ' --' = ' --'”,

which yields exit code 0. The shell executes the first “then”
branch of the “if” compound command: It executes the command
“shift”, that is, it discards the positional parameter “"$1"”. So
the remaining positional parameters are these ones:

“"$1"” expands to “no”,
“"$2"” expands to “variable”,
“"$3"” expands to “assignments,”,
“"$4"” expands to “just”,
“"$5"” expands to “additional”, and
“"$6"” expands to “arguments”.

Then it executes the “break” command, exiting the loop. Finally,
it executes the “printf” command, as if it had been started by the
command line

“printf '%s\n' 'These are the positional parameters:' \
no variable assignments, just additional arguments”.
Hongyi Zhao
2017-06-24 00:59:48 UTC
Permalink
On Fri, 23 Jun 2017 13:45:45 +0200, Helmut Waitzmann wrote:
[snipped]
Post by Helmut Waitzmann
-s -- -- no variable assignments, just additional arguments \
3<<\EOF while test "$#" -ge 1 do
if test " $1" = ' --'
Why not using the following form without the space:

if test "$1" = '--'

[snipped]
Post by Helmut Waitzmann
Now it executes its command line: It redirects file descriptor #0 to
file descriptor #3 (which happens to be the here-document),
closes file descriptor #3,
I'm a newbie on the file descriptors' inner-communication / management /
mechanism and asked the following maybe naive question:

It seems the file descriptor #3 will be closed immediately after
redirecting file descriptor #0 to it.

Does we need close the file descriptor #3 after all of the here-document
has been read successfully?
Post by Helmut Waitzmann
and starts (assuming the environment variable
“SHELL” has got the value “/bin/bash”) a “/bin/bash”,
giving it the arguments “-s”, “--”, “--”, “no”, “variable”,
“assignments,”, “just”, “additional”, and “arguments” , as if it had
started it by the command line
[snipped]

Thanks a lot for your deep-in analysis and giving mature consideration to
all aspects of this question :-)

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-06-24 20:42:59 UTC
Permalink
Post by Hongyi Zhao
[snipped]
Post by Helmut Waitzmann
-s -- -- no variable assignments, just additional arguments \
3<<\EOF while test "$#" -ge 1 do
if test " $1" = ' --'
if test "$1" = '--'
I'm just cautious here. Some historical implementations of “test”
behaved wrong, when "$1" was '-', '(', '!' or '='.

See the comment in the APPLICATION USAGE section of the
specification of the POSIX “test” utility
(<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html#tag_20_128_16>):

Historical systems have also been unreliable given the common
construct:

test "$response" = "expected string"

One of the following is a more reliable form:

test "X$response" = "Xexpected string"
test "expected string" = "$response"

Note that the second form assumes that expected string could not
be confused with any unary primary. If expected string starts
with '-', '(', '!', or even '=', the first form should be used
instead. Using the preceding rules without the XSI marked
extensions, any of the three comparison forms is reliable, given
any input. (However, note that the strings are quoted in all
cases.)
Hongyi Zhao
2017-06-25 00:12:47 UTC
Permalink
Post by Helmut Waitzmann
test "X$response" = "Xexpected string"
So, based on your notes here, both of the two forms are robust:

if test " $1" = ' --'
if test "X$1" = 'X--'
Post by Helmut Waitzmann
test "expected string" = "$response"
It seems this form is rarely used, even when it's safe/reliable.

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-06-25 01:20:30 UTC
Permalink
Post by Helmut Waitzmann
if test " $1" = ' --'
if test "X$1" = 'X--'
Yes. They are.
Helmut Waitzmann
2017-06-25 01:09:46 UTC
Permalink
Post by Hongyi Zhao
[snipped]
Post by Helmut Waitzmann
-s -- -- no variable assignments, just additional arguments \
3<<\EOF while test "$#" -ge 1 do
if test " $1" = ' --'
Now it executes its command line: It redirects file descriptor
#0 to file descriptor #3 (which happens to be the
here-document), closes file descriptor #3,
I'm a newbie on the file descriptors' inner-communication /
management / mechanism and asked the following maybe naive
It seems the file descriptor #3 will be closed immediately after
redirecting file descriptor #0 to it.
Yes.

In the operating system kernel, there is a system-wide open-files
table.

When a file is opened, the operating system allocates an entry in
the system-wide open-files table. Then it allocates an entry in
the process' file descriptor table. In this entry, it stores a
reference to the allocated entry in the system-wide open-files
table. Then it returns to the calling process the position of the
entry in the process' file descriptor table: the file descriptor
number.

In the entry in the system-wide open-files table some information
regarding that opened file is recorded: Is the file opened for
reading, for writing, or for both? Will writing to the file
overwrite or append data? What is the current access position in
the file?

Redirecting the file descriptor #0 to #3 means copying the
reference to the system-wide open-files table, which has been
stored in the entry #3 of the process' file descriptor table to
the entry #0 of the process' file descriptor table. So, file
descriptor redirecting actually is a misnomer. The file
descriptors #0 and #3 just refer to the same entry in the
system-wide open-files table.

When a file descriptor is closed, the operating system frees the
entry in the process' file descriptor table.

If all file descriptors, that refer to the same entry in the
system-wide open-file table, have been closed, then the operating
system frees that entry in the system-wide open-file table as
well.
Post by Hongyi Zhao
Does we need close the file descriptor #3 after all of the
here-document has been read successfully?
Not only, after the here-document has been read: The file descriptor
#3 can and should be closed as soon as it has been copied to
the file descriptor #0.

I think, this is a sort of file descriptor hygiene: Close all file
descriptors you know of that no one will need or may use any
longer.

As the shell has duplicated the file descriptor #3 to the file
descriptor #0, it does not need the file descriptor #3 any longer.
By closing it, it frees it for other use.

If the shell didn't close the file descriptor #3, it might be
inherited by the called “"$SHELL"”, which even doesn't know of
that file descriptor.

See also <https://en.wikipedia.org/wiki/File_descriptor#top>.
Hongyi Zhao
2017-06-25 06:07:00 UTC
Permalink
Redirecting the file descriptor #0 to #3 means copying the reference to
the system-wide open-files table, which has been stored in the entry #3
of the process' file descriptor table to the entry #0 of the process'
file descriptor table. So, file descriptor redirecting actually is a
misnomer. The file descriptors #0 and #3 just refer to the same entry
in the system-wide open-files table.
Ok, let's return to the original question of mine in this thread and the
answer give by you:

----------------- quoting begin from here -------------------
Path:
aspen.stu.neva.ru!goblin1!goblin.stu.neva.ru!news.albasani.net!.POSTED!
not-
for-mail
From: Helmut Waitzmann <***@xoxy.net>
Newsgroups: comp.unix.shell
Subject: Re: su: must be run from a terminal
Date: Tue, 14 Mar 2017 06:54:47 +0100
Organization: albasani.net
Lines: 59
Message-ID: <***@helmutwaitzmann.news.arcor.de>
References: <oa68hg$p7t$***@dont-email.me>
Reply-To: Helmut Waitzmann Anti-Spam-Ticket.b.qc3c <***@xoxy.net>
Mime-Version: 1.0
X-Trace: news.albasani.net
cRasz/UNczw/UVgxZ2E31j9ggcKNoh9/
oRQM69nngpOxfVckXMq21beJbiyVau1neczgcbj/765
hopdS2M5NstbuUS5vKnHss8CfUwHnCzVmZs7ee4dZ5q5K5BCR+mJO
NNTP-Posting-Date: Tue, 14 Mar 2017 05:55:13 +0000 (UTC)
Injection-Info: news.albasani.net;
logging-data="o/liCWvBqI8IUbj26wofp8hCt553VgOzHzrQGEdY/
Vh9oBHGMWOjd1QRgoCCV
rn5FczrSrqFRx+Xday0MgdspGkIFVwdhph63pbX/
jbWtTyzmXt3nk9yuXYbcAhz6SSc";
mail-complaints-to="***@albasani.net"
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/23.2 (gnu/linux)
Mail-Reply-To: Helmut Waitzmann Anti-Spam-Ticket.b.qc3c
<***@xoxy.net>
Cancel-Lock: sha1:HdxHPtVuCU6JrJV+dLEk628vn9Y=
sha1:NKUxPObHgj4vHfQt5UjNDHSr7/c=
Mail-Copies-To: nobody
Xref: aspen.stu.neva.ru comp.unix.shell:49883
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Hi all,
I try to use su to execute some commands with heredoc in my script like
su root <<EOF
bla
bla
...
EOF
As there is no means to tell su to redirect standard input to file
descriptor #3 after having read the password from standard input
but before executing the shell, the redirection has to be done by
the shell that is executed by su.

As I understand now, you draw the above conclusion is based on the
following fact:

The #0 is already used for reading the password from standard input (in
kernel, the standard input is always #0). So, the su cann't using #0 for
reading the here-doc from standard input at the same time.

When using the here-doc to input some stuff to to su, considering that
the #0 is preserved to used to su for reading the password from standard
input, as a result, the kernel must pick up a FD greater than 2 for this
purpose.

Am I right?

As far as I understand one can't tell a shell to redirect file
descriptor #0 to file descriptor #3 and then read commands from
standard input.

This is because we cann't use the same file descriptor to do different
things at the same time, i.e., reading the password from stdin and
reading the here-doc from stin meanwhile.

Am I right?

But one can tell a shell to redirect descriptor #0 to file
descriptor #3 and then start a new shell.

By this method, we will let the original su read the password from the
#0, while the new started shell will be sub-shell and it will be
allocated a new #0 which is different from the original su's #0.

So, we can redirect the new shell's descriptor #0 to the file
descriptor #3.

Am I right?
----------------- quoting ends here -------------------

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-06-25 23:53:42 UTC
Permalink
Post by Hongyi Zhao
Redirecting the file descriptor #0 to #3 means copying the reference to
the system-wide open-files table, which has been stored in the entry #3
of the process' file descriptor table to the entry #0 of the process'
file descriptor table. So, file descriptor redirecting actually is a
misnomer. The file descriptors #0 and #3 just refer to the same entry
in the system-wide open-files table.
Ok, let's return to the original question of mine in this thread and the
----------------- quoting begin from here -------------------
Newsgroups: comp.unix.shell
Subject: Re: su: must be run from a terminal
Date: Tue, 14 Mar 2017 06:54:47 +0100
Organization: albasani.net
Lines: 59
Mime-Version: 1.0
X-Trace: news.albasani.net
cRasz/UNczw/UVgxZ2E31j9ggcKNoh9/
oRQM69nngpOxfVckXMq21beJbiyVau1neczgcbj/765
hopdS2M5NstbuUS5vKnHss8CfUwHnCzVmZs7ee4dZ5q5K5BCR+mJO
NNTP-Posting-Date: Tue, 14 Mar 2017 05:55:13 +0000 (UTC)
Injection-Info: news.albasani.net;
logging-data="o/liCWvBqI8IUbj26wofp8hCt553VgOzHzrQGEdY/
Vh9oBHGMWOjd1QRgoCCV
rn5FczrSrqFRx+Xday0MgdspGkIFVwdhph63pbX/
jbWtTyzmXt3nk9yuXYbcAhz6SSc";
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/23.2 (gnu/linux)
Mail-Reply-To: Helmut Waitzmann Anti-Spam-Ticket.b.qc3c
Cancel-Lock: sha1:HdxHPtVuCU6JrJV+dLEk628vn9Y=
sha1:NKUxPObHgj4vHfQt5UjNDHSr7/c=
Mail-Copies-To: nobody
Xref: aspen.stu.neva.ru comp.unix.shell:49883
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Hi all,
I try to use su to execute some commands with heredoc in my script like
su root <<EOF
bla
bla
...
EOF
As there is no means to tell su to redirect standard input to file
descriptor #3 after having read the password from standard input
but before executing the shell, the redirection has to be done by
the shell that is executed by su.
As I understand now, you draw the above conclusion is based on the
The #0 is already used for reading the password from standard input
Yes. “su” expects, that in its file descriptor table entry #0
there is a reference into the system-wide open-files table, which
denotes a terminal, opened for reading. When “su” reads the
password, it calls the system service “read()” (see the manual
page “read(2)”) with the file descriptor #0 as a parameter.
Post by Hongyi Zhao
(in kernel, the standard input is always #0).
By convention, processes (except daemon processes) expect the file
descriptor #0 be opened for reading. Therefore, the file
descriptor #0 is called standard input. It's merely a naming
convention. There is nothing special with the file descriptor #0.
Post by Hongyi Zhao
So, the su cann't using #0 for reading the here-doc from
standard input at the same time.
Yes. Each file descriptor denotes one stream of data.
Post by Hongyi Zhao
When using the here-doc to input some stuff to to su, considering that
the #0 is preserved to used to su for reading the password from standard
input, as a result, the kernel must pick up a FD greater than 2 for this
purpose.
Am I right?
I'm not sure, if I understand you correctly.

“su” tests, whether the file descriptor #0 is opened (at least)
for reading and associated with a terminal. If that is not the
case (for example, a here-document), “su” complains and exits.
Try the command

“su -- root <<\EOF
EOF

Post by Hongyi Zhao
As far as I understand one can't tell a shell to redirect file
descriptor #0 to file descriptor #3 and then read commands from
standard input.
This is because we cann't use the same file descriptor to do different
things at the same time, i.e., reading the password from stdin and
reading the here-doc from stin meanwhile.
Am I right?
What I wanted to say, is: One can't tell a running shell to
change its command source by letting it redirect the file
descriptor #0. But I'm not sure about that.
Post by Hongyi Zhao
But one can tell a shell to redirect descriptor #0 to file
descriptor #3 and then start a new shell.
By this method, we will let the original su read the password from the
#0, while the new started shell will be sub-shell and it will be
allocated a new #0 which is different from the original su's #0.
Yes.
Post by Hongyi Zhao
So, we can redirect the new shell's descriptor #0 to the file
descriptor #3.
Am I right?
Yes.
Hongyi Zhao
2017-06-26 00:54:12 UTC
Permalink
Post by Helmut Waitzmann
Post by Hongyi Zhao
When using the here-doc to input some stuff to to su, considering that
the #0 is preserved to used to su for reading the password from
standard input, as a result, the kernel must pick up a FD greater than
2 for this purpose.
Am I right?
I'm not sure, if I understand you correctly.
“su” tests, whether the file descriptor #0 is opened (at least)
for reading and associated with a terminal.
How to test a FD is opened and associated with a terminal instead of
associating with a pipe / socket / file, etc?
Post by Helmut Waitzmann
If that is not the case
(for example, a here-document), “su” complains and exits. Try the
command
“su -- root <<\EOF
EOF ”
$ su -- root <<\EOF
ls
Post by Helmut Waitzmann
EOF
su: must be run from a terminal
$

According to my understanding now, this is because the #0 is opened for
reading the here-doc, and as a result, the su cann't bind it with a
terminal.

Please correct me if I'm wrong ;-)

Thanks a lot again.

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-06-27 05:05:25 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
Post by Hongyi Zhao
When using the here-doc to input some stuff to to su, considering that
the #0 is preserved to used to su for reading the password from
standard input, as a result, the kernel must pick up a FD greater than
2 for this purpose.
Am I right?
I'm not sure, if I understand you correctly.
“su” tests, whether the file descriptor #0 is opened (at least)
for reading and associated with a terminal.
How to test a FD is opened and associated with a terminal instead of
associating with a pipe / socket / file, etc?
The system library function “isatty()” does that.
Post by Hongyi Zhao
Post by Helmut Waitzmann
If that is not the case
(for example, a here-document), “su” complains and exits. Try the
command
“su -- root <<\EOF
EOF ”
$ su -- root <<\EOF
ls
Post by Helmut Waitzmann
EOF
su: must be run from a terminal
$
According to my understanding now, this is because the #0 is opened for
reading the here-doc,
Yes. But to be precise: A process can't open a file descriptor,
a process can open files.

For opening files, the kernel maintains two data structures: a
system-wide table of opened files, and a per-process table of file
descriptors. When a process asks the kernel to open a file, the
kernel picks an unused entry of the process' table of file
descriptors.

Then it picks an unused entry of the system-wide table of open
files and records the number of that entry in the allocated entry
of the process' table of file descriptors.

In the allocated entry of the system-wide table of open files, the
kernel records, which file is to be accessed, the access mode
(that is, whether the file is opened for reading or for writing,
if writing will overwrite the file or append to it, etc.), the
current access position in the opened file, and, how many file
descriptors are associated with this entry of the system-wide
table of open files (in this case: 1).

Finally, the kernel returns the index of the allocated entry of
the process' file descriptor table, a small non-negative number,
to the process. For example, the return value of the system call
“open()” will return such a number. See the manual page “open(2)”.

Note: The process can't (directly) tell the kernel, which entry
of the file descriptors table to use, when opening a file.

But there is a system service, which allows a process to tell the
kernel, which entry of the file descriptors table to use:
“dup2()”. See the manual page “dup2(2)”.
Post by Hongyi Zhao
and as a result, the su cann't bind it with a terminal.
“su” could do (but does not; “sudo” by default does) open
“/dev/tty"”. Then there would be no need to read the password
From the opened file associated with file descriptor #0.
Kaz Kylheku
2017-06-27 14:12:04 UTC
Permalink
Post by Helmut Waitzmann
Post by Hongyi Zhao
How to test a FD is opened and associated with a terminal instead of
associating with a pipe / socket / file, etc?
The system library function “isatty()” does that.
How is that useful in comp.unix.shell?

In shell programming, the POSIX-standard test operator -t does that.

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html

-t file_descriptor
True if file descriptor number file_descriptor is open and is
associated with a terminal. False if file_descriptor is not a valid
file descriptor number, or if file descriptor number file_descriptor
is not open, or if it is open but is not associated with a terminal.

Basically a wrapper for isatty.
Barry Margolin
2017-06-27 14:50:45 UTC
Permalink
Post by Kaz Kylheku
Post by Hongyi Zhao
How to test a FD is opened and associated with a terminal instead of
associating with a pipe / socket / file, etc?
The system library function “isatty()” does that.
How is that useful in comp.unix.shell?
The question was in response to a statement that "su" does this test, so
I think he was asking how it does it, not how he would do it in the
shell.
--
Barry Margolin, ***@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
Hongyi Zhao
2017-06-22 02:17:54 UTC
Permalink
Post by Helmut Waitzmann
“LC_ALL=C expr " $1" : ' [[:alpha:]_][[:alnum:]_]*=' > /dev/null”
tests for.
IMO, without using the expr command, the above also can be done by the ``
[[ ... ]]'' construction within bash:

“LC_ALL=C [[ " $1" =~ ' [[:alpha:]_][[:alnum:]_]*=' ]] > /dev/null”

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Helmut Waitzmann
2017-06-22 15:49:35 UTC
Permalink
Post by Hongyi Zhao
Post by Helmut Waitzmann
“LC_ALL=C expr " $1" : ' [[:alpha:]_][[:alnum:]_]*=' > /dev/null”
tests for.
IMO, without using the expr command, the above also can be done
“LC_ALL=C [[ " $1" =~ ' [[:alpha:]_][[:alnum:]_]*=' ]] > /dev/null”
As bash's “[[ … =~ … ]]” construct is not covered by the POSIX
standard, I haven't used it yet and don't know exactly, what it does.

Prepending “LC_ALL=C” won't work anyway, because “[[ … ]]” is a
compound command (“[[” and “]]” are reserved words, whereas “[”
and “test” are not):

Letting bash execute the command line

“LC_ALL=C [[ ' var=value' =~ ' [[:alpha:]_][[:alnum:]_]*=' ]]”

results in the following error message:

bash: [[: command not found

This is because the “LC_ALL=C” variable assignment forces “[[” to
be searched for as a simple command.
Hongyi Zhao
2017-06-23 03:05:20 UTC
Permalink
As bash's “[[ … =~ … ]]” construct is not covered by the POSIX standard,
I haven't used it yet and don't know exactly, what it does.
Prepending “LC_ALL=C” won't work anyway, because “[[ … ]]” is a compound
command (“[[” and “]]” are reserved words, whereas “[”
Letting bash execute the command line
“LC_ALL=C [[ ' var=value' =~ ' [[:alpha:]_][[:alnum:]_]*=' ]]”
bash: [[: command not found
This is because the “LC_ALL=C” variable assignment forces “[[” to be
searched for as a simple command.
Thanks a lot for your explanation.

Regards
--
.: Hongyi Zhao [ hongyi.zhao AT gmail.com ] Free as in Freedom :.
Continue reading on narkive:
Loading...