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 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.
Linux: why does launching /bin/bash as a subprocess from Python3 (or other interpreters) make the parent process immune to SIGINT (ctrl-c)?
bash runs in a different process group than your python process and takes over as the foreground process which makes it receive the signals while the parent doesn't.
"The setpgid()
and getpgrp()
calls are used by programs such as bash(1)
to create process groups in order to implement shell job control."
You can check the process group with ps o pid,pgrp <python-pid> <subprocess-pid>
. For a regular sub-process, you'll see the same process group for both the python script and the sub-process, while some programs, like bash, creates a new process group.
bash also installs its own signal handlers.
Example on Linux:
root# grep ^Sig /proc/$SUBPROCESS_PID/status
SigPnd: 0000000000000000 # pending
SigBlk: 0000000000000000 # blocked
SigIgn: 0000000000380004 # ignored
SigCgt: 000000004b817efb # caught
The SigCgt
field is the interesting. It's a bitmask:
$ bc <<< "ibase=16; obase=2; 4B817EFB"
1001011100000010111111011111011
|
SIGINT
You can create a program the does the same thing as bash. Example:
// sig.cpp
#include <unistd.h>
#include <cerrno>
#include <csignal>
#include <cstring>
#include <iostream>
#include <stdexcept>
static int gsig = -1; // the latest caught signal
static void sighandler(int sig) {
gsig = sig;
}
int check(int val) {
if(val) std::runtime_error(std::strerror(errno));
return val;
}
int main() {
try {
// catch a lot...
for(auto sig : {SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM}) {
check(std::signal(sig, sighandler)==SIG_ERR);
}
/* ignore terminal settings changes */
check(signal(SIGTTOU, SIG_IGN)==SIG_ERR);
// create new process group
check(::setpgrp());
// get the created process group
pid_t pgrp = ::getpgrp();
// set forground process group to the created process group
check(::tcsetpgrp(::fileno(stdin), pgrp));
std::cout << "-- waiting --" << std::endl;
while(true) {
::pause();
std::cout << "got signal " << gsig << std::endl;
}
} catch(const std::exception& ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
}
}
Compile
$ g++ -o sig sig.cpp -std=c++11 -O3
If you now put this program in your ./parent.py
script, you'll see a similar behavior as that of bash.
${1:+$@} in /bin/sh
'Hysterical Raisins', aka Historical Reasons.
The explanation from JesperE (or the Bash man page on shell parameter expansion) is accurate for what it does:
- If
$1
exists and is not an empty string, then substitute the quoted list of arguments.
Once upon 20 or so years ago, some broken minor variants of the Bourne Shell substituted an empty string ""
for "$@"
if there were no arguments, instead of the correct, current behaviour of substituting nothing. Whether any such systems are still in use is open to debate.
[Hmm: that expansion would not work correctly for:
command '' arg2 arg3 ...
In this context, the correct notation is:
${1+"$@"}
This works correctly whether $1
is an empty argument or not. So, someone remembered the notation incorrectly, accidentally introducing a bug.]
Why does read behave differently with the same input?
How about using $'\c'
:
man bash | less -p '\\c * suppress trailing newline'
str=""
while read line; do echo "line=$line"; done <<<$'\c'"${str}"
str="abc"
while read line; do echo "line=$line"; done <<<$'\c'"${str}"
I can't use built-in bash commands when running a script using 'sh'
You could think about it like this:
There are many "human languages" (French, Japanese, English, Hindi etc)
There are many different "shell languages" (sh, csh, tcsh, zsh, bash etc)
Think of sh
and bash
as languages, not commands.
The errors you are getting is because your computer is expecting you to talk to it in sh
, but actually you are talking to it in bash
. It is like giving a French document to a German translator....
So, to resolve this, you just need to inform your computer that your script is written in bash
.
To do this, simply add this line to the very top of your script file:
#!/bin/bash
Related Topics
Simulate Delayed and Dropped Packets on Linux
The 'Eval' Command in Bash and Its Typical Uses
How to Parse Xml Using Shellscript
Docker Can't Connect to Docker Daemon
How to Join Multiple Lines of File Names into One With Custom Delimiter
How to Run Script as Another User Without Password
Command Line Program to Create Website Screenshots (On Linux)
How to Use Sed to Change My Configuration Files, With Flexible Keys and Values
Cronjob Does Not Execute a Script That Works Fine Standalone
How to Print the Ld(Linker) Search Path
Use Sudo With Password as Parameter
Linking Openssl Libraries to a Program
Why Do X86-64 Linux System Calls Modify Rcx, and What Does the Value Mean
Retaining File Permissions With Git
How to Print a Number in Assembly Nasm
How to Remove Cached Credentials from Git