Why does bash behave differently, when it is called as sh?
When bash
is invoked as sh
, it (mostly) restricts itself to features found in the POSIX standard. Process substitution is not one of those features, hence the error.
Why does /bin/sh behave differently to /bin/bash even if one points to the other?
bash
looks at the value of $argv[0]
(bash is implemented in C) to determine how it was invoked.
Its behavior when invoked as sh
is documented in the manual:
If Bash is invoked with the name
sh
, it tries to mimic the startup
behavior of historical versions ofsh
as closely as possible, while
conforming to the POSIX standard as well.When invoked as an interactive login shell, or as a non-interactive
shell with the-login
option, it first attempts to read and execute
commands from/etc/profile
and~/.profile
, in that order. The
--noprofile
option may be used to inhibit this behavior. When invoked as an interactive shell with the namesh
, Bash looks for the variable
ENV
, expands its value if it is defined, and uses the expanded value
as the name of a file to read and execute. Since a shell invoked assh
does not attempt to read and execute commands from any other startup
files, the--rcfile
option has no effect. A non-interactive shell
invoked with the namesh
does not attempt to read any other startup
files.When invoked as
sh
, Bash enters POSIX mode after the startup files are
read
There's a long list (currently 46 items) of things that change when bash
is in POSIX mode, documented here.
(POSIX mode is probably useful mostly as a way to test scripts for portability to non-bash
shells.)
Incidentally, programs that change their behavior depending on the name under which they were invoked are fairly common. Some versions of grep
, fgrep
, and egrep
are implemented as a single executable (though GNU grep
doesn't do this). view
is typically a symbolic link to vi
or vim
; invoking it as view
causes to open in read-only mode. The Busybox system includes a number of individual commands that are all symlinks to the master busybox
executable.
Why does sed behave differently in a shell script?
The issue was simply that I had a long-forgotten bash alias changing sed
to gsed
— the GNU version as installed by Homebrew. That explains why sed --version
reported itself as gsed
at the command line. I had checked which sed
from both the script and the prompt, but I didn't think about type
and bash aliases.
$ type sed
sed is aliased to `gsed'
Why does /bin/sh behave differently on Mac and Ubuntu
- Why the /bin/sh behaves differently on on Mac and Ubuntu?
because they are both different shells. sh on ubuntu is the dash shell, sh on minix is the ash shell, sh on slackware is the bash shell. And since not too long is sh on OSX also the bash shell
If you want same behaviour, specify your shell... #!/bin/sh is, although strictly the never-in-free-systems-implemented-bourne-shell, generally the system shell which can vary from system to system ...use #!/bin/bash to literally say that you mean bash, use #!/bin/ksh to literally call the korn-shell etc.
- If I want to write the cross-platforma sh script, how should I avoid the platform-dependent code?
officially: keep strict to the POSIX rules to run on any POSIX compliant shell
pragmatically: write specific for one type of shell that is available on almost any system (not counting microcontrollers and SoC's or big irons and sparc stations,
bash is available for most systems, but yes there are system who don't do bash so bash is not the best choice for 100% portability, but it is absolutely the most (ab)used ;-)best portable: As an Ubuntu user rule of thumb: if it runs on dash (the /bin/sh of ubuntu) it will run on virtually anything (including your router , your toaster and your coffeemachine).
Above all: user2719058 is right, OSX is not Linux but BSD-UNIX so while they can run the same shell, the commands are just different enough to make it very very difficult to write a one-script-fits-all. Choosing the best shell will not change that....so the value of a unified scripting language is hereby proven to be very limited unless the system commands on every system are POSIX compliant as well.
tl;dr:
Unified cross-platform scripting is a pipe dream because differences in binaries across systems prevent this.
Why is Bash handling child processes different compared to Sh
Answered thanks to chepner and Charles Duffy:
bash -c
has an implicit optimization where it uses exec
to replace itself if possible. sh
(dash
) does not have this optimization. See also this observation.
To verify:
- Process tree using
bash
:
❯ docker run --name test --rm --detach krallin/ubuntu-tini bash -c 'sleep 60'
03194d48a4dcc8225251fe1e5de2dcbb901c8a9cfd0853ae910bfe4d3735608d
❯ docker exec test ps axfo pid,ppid,args
PID PPID COMMAND
1 0 /usr/bin/tini -- bash -c sleep 60
7 1 sleep 60
- Process tree using
sh
:
❯ docker run --name test --rm --detach krallin/ubuntu-tini sh -c 'sleep 60'
e56f207509df4b0b57f8e6b2b2760835f6784a147b200d798dffad112bb11d6a
❯ docker exec test ps axfo pid,ppid,args
PID PPID COMMAND
1 0 /usr/bin/tini -- sh -c sleep 60
7 1 sh -c sleep 60
8 7 \_ sleep 60
Why does printf behave differently when called from a Makefile?
@chepner is on the right track in their comment but the details are not quite right:
This is wild speculation, but I suspect there is some sort of
optimization being applied bymake
that causes the first example, as a
simple command, to be executing a third option, the actual binaryprintf
(found in/usr/bin
, perhaps), rather than a shell. In your
second example, the;
forcesmake
to use a shell to execute the shell
command line.
Make always uses /bin/sh
as its shell, regardless of what the user is using as their shell. On some systems, /bin/sh
is bash (which has a builtin printf
) and on some systems /bin/sh
is something different (typically dash
which is a lightweight, POSIX-conforming shell) which probably doesn't have a shell built-in.
On your system, /bin/sh
is bash. But, when you have a "simple command" that doesn't require a shell (that is, make itself has enough trivial quoting smarts to understand your command) then to be more efficient make will invoke that command directly rather than running the shell.
That's what's happening here: when you run the simple command (no ;
) make will invoke the command directly and run /usr/bin/printf
. When you run the more complex command (including a ;
) make will give up running the command directly and invoke your shell... which is bash, which uses bash's built-in printf
.
Basically, your script is not POSIX-conforming (there is no %b
in the POSIX standard) and so what it does is not well-defined. If you want the SAME behavior always you should use /usr/bin/printf
to force that always to be used. Forcing make to always run a shell and never use its fast path is much trickier; you'll need to include a special character like a trailing ;
in each command.
Related Topics
How to Run Multiple Programs in a Sequence
R Programming - Submitting Jobs on a Multiple Node Linux Cluster Using Pbs
Is an Operating System Kernel an Interpeter for All Other Programs
How to Properly Quote This Bash Pipeline for Watch
How Provide Nested Mount of Overlayfs
Print the Output of Strace Command in a Text File
Bash Shell: Cannot Use Variable $ as a Path to Run Tar
Gitlab Ssh Requests Password and Ignoring Ssh Keys
Why Does Script Not Recognize File Extension
How to Pass a Complete Argument List in Bash While Keeping Mulitword Arguments Together
Using Gzip to Compress Files to Transfer with Aws Command
Replace Bash Variables in Template File
Linux Script Start,Stop,Restart