Consider this example instead:
$ cat f
grep pos /proc/self/fdinfo/0
IFS= read -r var
echo A
echo B
printf '%s\n' "var=$var"
$ bash < f
pos: 29
B
var=echo A
$ dash < f
pos: 85
A
B
var=
As you can see, at the time the grep
command is run, the position within stdin is at the end of the file with dash
, and just after the newline that follows the grep
command in bash
.
The echo A
command is run by dash
but in the case of bash
, it's fed as input to read
.
What happened is that dash
read the whole input (actually, a block of text) while bash
read one line at a time before running commands.
To do that, bash
would need to read one byte at a time to make sure it doesn't read past the newline, but when the input is a regular file (like in the case of my f
file above, but also for here-documents which bash implements as temporary files, while dash
uses pipes), bash
optimises it by reading by blocks and seek back to the end of the line, which you can see with strace
on Linux:
$ strace -e read,lseek bash < f
[...]
lseek(0, 0, SEEK_CUR) = 0
read(0, "grep pos /proc/self/fdinfo/0\nIFS"..., 85) = 85
lseek(0, -56, SEEK_CUR) = 29
pos: 29
[...]
$ strace -e read,lseek dash < f
read(0, "grep pos /proc/self/fdinfo/0\nIFS"..., 8192) = 85
pos: 85
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12422, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
read(0, "", 1) = 0
[...]
When stdin is a terminal device, each read()
returns lines as sent by the terminal, so you generally see a similar behaviour in bash
and dash
.
In your case, you could do:
sudo dash << 'end-of-script'
su test <<"end"
whoami
end
end-of-script
or better:
sudo sh -c '
su test -c whoami
'
or even better:
sudo -u test whoami
echo 'rev\nuptime' | some_shell
. – marcelm Aug 25 '19 at 10:04printf 'rev\nuptime\n' | some_shell
– ilkkachu Aug 25 '19 at 11:54printf
. – marcelm Aug 25 '19 at 19:34