shell-expand-line
(\e\C-e
) expands command substitutions in bash.
$ bind -p|grep shell-ex
"\e\C-e": shell-expand-line
$(!!)\e\C-e
would run the previous command again and insert the output:
"\eo": "$(!!)\e\C-e"
It also expands other command substitutions, but there is no command like shell-expand-word
.
In bash 4.0 or later you could also enable globstar
, type **/file.txt
, and use glob-complete-word
(\eg
) or glob-expand-word
(\C-x*
).
You know, of course, that $(…)
causes the command(s) within the parentheses
to run in a subshell. And you know, of course, that jobs
is a shell builtin.
Well, it looks like jobs
clears a job from the shell’s memory
once its death has been reported.
But, when you run $(jobs)
, the jobs
command runs in a subshell,
so it doesn’t get a chance to tell the parent shell
(the one that’s running the script) that the death of the background job
(ping
, in your example) has been reported.
So, each time the shell spawns a subshell to run the $(jobs)
thingie,
that subshell still has a complete list of jobs
(i.e., the ping
job is there, even though it’s dead after the 5th iteration),
and so jobs
still (again) believes that
it needs to report on the status of the ping
job
(even if it’s been dead for the past four seconds).
This explains why running an unadulterated jobs
command
within the loop causes it to exit as expected:
once you run jobs
in the parent shell, the parent shell knows that
the job’s termination has been reported to the user.
Why is it different in the interactive shell?
Because, whenever a foreground child of an interactive shell terminates,
the shell reports on any background jobs that have terminated1
while the foreground process was running.
So, the ping
terminates while the sleep 1
is running,
and when the sleep
terminates,
the shell reports on the background job’s death.
Et voilà.
1 It might be more accurate to say “any background jobs
that have changed state while the foreground process was running.”
I believe that it might also report on jobs that have been suspended
(kill -SUSP
, the programmatic equivalent to Ctrl+Z)
or become unsuspended (kill -CONT
, which is what the fg
command does).
Best Answer
A subshell starts out as an almost identical copy of the original shell process. Under the hood, the shell calls the
fork
system call1, which creates a new process whose code and memory are copies2. When the subshell is created, there are very few differences between it and its parent. In particular, they have the same variables. Even the$$
special variable keeps the same value in subshells: it's the original shell's process ID. Similarly$PPID
is the PID of the parent of the original shell.A few shells change a few variables in the subshell. Bash sets
BASHPID
to the PID of the shell process, which changes in subshells. Bash, zsh and mksh arrange for$RANDOM
to yield different values in the parent and in the subshell. But apart from built-in special cases like these, all variables have the same value in the subshell as in the original shell, the same export status, the same read-only status, etc. All function definitions, alias definitions, shell options and other settings are inherited as well.A subshell created by
(…)
has the same file descriptors as its creator. Some other means of creating subshells modify some file descriptors before executing user code; for example, the left-hand side of a pipe runs in a subshell3 with standard output connected to the pipe. The subshell also starts out with the same current directory, the same signal mask, etc. One of the few exceptions is that subshells do not inherit custom traps: ignored signals (trap '' SIGNAL
) remain ignored in the subshell, but other traps (trap CODE
SIGNAL) are reset to the default action4.A subshell is thus different from executing a script. A script is a separate program. This separate program might coincidentally be also a script which is executed by the same interpreter as the parent, but this coincidence doesn't give the separate program any special visibility on internal data of the parent. Non-exported variables are internal data, so when the interpreter for the child shell script is executed, it doesn't see these variables. Exported variables, i.e. environment variables, are transmitted to executed programs.
Thus:
prints
1
because the subshell is a replication of the shell that spawned it.happens to run a shell as a child process of a shell, but the
x
on the second line has no more connection with thex
on the second line than inor
1 An exception is the
ksh93
shell where the forking is optimised out and most of its side effects are emulated.2 Semantically, they're copies. From an implementation perspective, there's a lot of sharing going on.
3 For the right-hand side, it depends on the shell.
4 If you test this out, note that things like
$(trap)
may report the traps of the original shell. Note also that many shells have bugs in corner cases involving traps. For example ninjalj notes that as of bash 4.3,bash -x -c 'trap "echo ERR at \$BASH_SUBSHELL \$BASHPID" ERR; set -E; false; echo one subshell; (false); echo two subshells; ( (false) )'
runs theERR
trap from the nested subshell in the “two subshells” case, but not theERR
trap from the intermediate subshell —set -E
option should propagate theERR
trap to all subshells but the intermediate subshell is optimized away and so isn't there to run itsERR
trap.