Dans ksh, un sous-shell peut ou non entraîner un nouveau processus. Je ne sais pas quelles sont les conditions, mais le shell a été optimisé pour les performances sur les systèmes où fork()
était plus cher qu'il ne l'est généralement sous Linux, il évite donc de créer un nouveau processus chaque fois que cela est possible. La spécification parle d'un "nouvel environnement", mais cette séparation environnementale peut être effectuée en cours de processus.
Une autre différence vaguement liée est l'utilisation de nouveaux procédés pour les tuyaux. Dans ksh et zsh, si la dernière commande d'un pipeline est une commande intégrée, elle s'exécute dans le processus shell actuel, donc cela fonctionne :
$ unset x
$ echo foo | read x
$ echo $x
foo
$
Dans bash, toutes les commandes de pipeline après la première sont exécutées dans des sous-shells, donc ce qui précède ne fonctionne pas :
$ unset x
$ echo foo | read x
$ echo $x
$
Comme le souligne @dave-thompson-085, vous pouvez obtenir le comportement ksh/zsh dans les versions 4.2 et ultérieures de bash si vous désactivez le contrôle des tâches (set +o monitor
) et activez le lastpipe
choix (shopt -s lastpipe
). Mais ma solution habituelle consiste à utiliser la substitution de processus à la place :
$ unset x
$ read x < <(echo foo)
$ echo $x
foo
ksh93 travaille exceptionnellement dur pour éviter les sous-shells. Une partie de la raison est l'évitement de stdio et l'utilisation intensive de sfio qui permet aux commandes intégrées de communiquer directement. Une autre raison est que ksh peut en théorie avoir autant de fonctions intégrées. Si construit avec SHOPT_CMDLIB_DIR
, toutes les commandes intégrées cmdlib sont incluses et activées par défaut. Je ne peux pas donner une liste complète des endroits où les sous-shells sont évités, mais c'est généralement dans des situations où seuls les builtins sont utilisés et où il n'y a pas de redirections.
#!/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]"
sortie :
Version AJM 93v- 2013-02-22
31732 31732
31735 31732
31736 31732
31732 31732
31732 31732
31732 31732
31732 31732
31738 31732
Une autre conséquence intéressante de toute cette gestion interne des E/S est que certains problèmes de mise en mémoire tampon disparaissent. Voici un exemple amusant de lecture de lignes avec tee
et head
builtins (n'essayez pas cela dans un autre 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 page de manuel bash indique :
Chaque commande d'un pipeline est exécutée en tant que processus distinct (c'est-à-dire dans un sous-shell).
Bien que cette phrase concerne les canaux, elle implique fortement qu'un sous-shell est un processus distinct.
La page d'homonymie de Wikipedia décrit également un sous-shell en termes de processus enfant. Un processus enfant est certainement lui-même un processus.
La page de manuel de ksh (en un coup d'œil) n'est pas directe sur sa propre définition d'un sous-shell, elle n'implique donc pas d'une manière ou d'une autre qu'un sous-shell est un processus différent.
Apprendre le Korn Shell dit qu'il s'agit de processus différents.
Je dirais qu'il vous manque quelque chose (ou que le livre est erroné ou obsolète).