In ksh, una subshell potrebbe o meno risultare in un nuovo processo. Non so quali siano le condizioni, ma la shell è stata ottimizzata per le prestazioni su sistemi in cui fork()
era più costoso di quanto non sia in genere su Linux, quindi evita di creare un nuovo processo ogni volta che può. La specifica dice un "nuovo ambiente", ma quella separazione ambientale può essere fatta durante il processo.
Un'altra differenza vagamente correlata è l'utilizzo di nuove lavorazioni per i tubi. In ksh e zsh, se l'ultimo comando in una pipeline è incorporato, viene eseguito nel processo shell corrente, quindi funziona:
$ unset x
$ echo foo | read x
$ echo $x
foo
$
In bash, tutti i comandi della pipeline dopo il primo vengono eseguiti in subshell, quindi quanto sopra non funziona:
$ unset x
$ echo foo | read x
$ echo $x
$
Come sottolinea @dave-thompson-085, puoi ottenere il comportamento ksh/zsh nelle versioni bash 4.2 e successive se disattivi il controllo del lavoro (set +o monitor
) e accendi il lastpipe
opzione (shopt -s lastpipe
). Ma la mia solita soluzione è usare invece la sostituzione del processo:
$ unset x
$ read x < <(echo foo)
$ echo $x
foo
ksh93 lavora in modo insolitamente duro per evitare le subshell. Parte del motivo è l'evitamento di stdio e l'uso estensivo di sfio che consente ai builtin di comunicare direttamente. Un altro motivo è che ksh può in teoria avere così tanti built-in. Se compilato con SHOPT_CMDLIB_DIR
, tutti i incorporati cmdlib sono inclusi e abilitati per impostazione predefinita. Non posso fornire un elenco completo di luoghi in cui le subshell vengono evitate, ma in genere è in situazioni in cui vengono utilizzati solo i built-in e dove non ci sono reindirizzamenti.
#!/usr/bin/env ksh
# doCompat arr
# "arr" is an indexed array name to be assigned an index corresponding to the detected shell.
# 0 = Bash, 1 = Ksh93, 2 = mksh
function doCompat {
${1:+:} return 1
if [[ ${BASH_VERSION+_} ]]; then
shopt -s lastpipe extglob
eval "${1}[0]="
else
case "${BASH_VERSINFO[*]-${!KSH_VERSION}}" in
.sh.version)
nameref v=$1
v[1]=
if builtin pids; then
function BASHPID.get { .sh.value=$(pids -f '%(pid)d'); }
elif [[ -r /proc/self/stat ]]; then
function BASHPID.get { read -r .sh.value _ </proc/self/stat; }
else
function BASHPID.get { .sh.value=$(exec sh -c 'echo $PPID'); }
fi 2>/dev/null
;;
KSH_VERSION)
nameref "_${1}=$1"
eval "_${1}[2]="
;&
*)
if [[ ! ${BASHPID+_} ]]; then
echo 'BASHPID requires Bash, ksh93, or mksh >= R41' >&2
return 1
fi
esac
fi
}
function main {
typeset -a myShell
doCompat myShell || exit 1 # stripped-down compat function.
typeset x
print -v .sh.version
x=$(print -nv BASHPID; print -nr " $$"); print -r "$x" # comsubs are free for builtins with no redirections
_=$({ print -nv BASHPID; print -r " $$"; } >&2) # but not with a redirect
_=$({ printf '%s ' "$BASHPID" $$; } >&2); echo # nor for expansions with a redirect
_=$(printf '%s ' "$BASHPID" $$ >&2); echo # but if expansions aren't redirected, they occur in the same process.
_=${ { print -nv BASHPID; print -r " $$"; } >&2; } # However, ${ ;} is always subshell-free (obviously).
( printf '%s ' "$BASHPID" $$ ); echo # Basically the same rules apply to ( )
read -r x _ <<<$(</proc/self/stat); print -r "$x $$" # These are free in {{m,}k,z}sh. Only Bash forks for this.
printf '%s ' "$BASHPID" $$ | cat # Sadly, pipes always fork. It isn't possible to precisely mimic "printf -v".
echo
} 2>&1
main "[email protected]"
fuori:
Version AJM 93v- 2013-02-22
31732 31732
31735 31732
31736 31732
31732 31732
31732 31732
31732 31732
31732 31732
31738 31732
Un'altra netta conseguenza di tutta questa gestione interna dell'I/O è che alcuni problemi di buffering scompaiono. Ecco un divertente esempio di lettura di righe con tee
e head
builtins (non provarlo in nessun'altra shell).
$ ksh -s <<\EOF
integer -a x
builtin head tee
printf %s\\n {1..10} |
while head -n 1 | [[ ${ { x+=("$(tee /dev/fd/{3,4})"); } 3>&1; } ]] 4>&1; do
print -r -- "${x[@]}"
done
EOF
1
0 1
2
0 1 2
3
0 1 2 3
4
0 1 2 3 4
5
0 1 2 3 4 5
6
0 1 2 3 4 5 6
7
0 1 2 3 4 5 6 7
8
0 1 2 3 4 5 6 7 8
9
0 1 2 3 4 5 6 7 8 9
10
0 1 2 3 4 5 6 7 8 9 10
La manpage di bash riporta:
Ogni comando in una pipeline viene eseguito come un processo separato (ovvero, in una subshell).
Sebbene questa frase riguardi le pipe, implica fortemente che una subshell sia un processo separato.
La pagina di disambiguazione di Wikipedia descrive anche una subshell in termini di processo figlio. Un processo figlio è certamente esso stesso un processo.
La manpage ksh (a colpo d'occhio) non è diretta sulla propria definizione di subshell, quindi non implica in un modo o nell'altro che una subshell sia un processo diverso.
Imparare la conchiglia Korn dice che sono processi diversi.
Direi che ti manca qualcosa (o che il libro è sbagliato o obsoleto).