Différences entre les sous-coquilles de bash et de ksh
j'ai toujours cru qu'un sous-shell n'était pas un processus d'enfant, mais un autre environnement shell dans le même processus.
j'utilise un ensemble de base de built-ins:
(echo "Hello";read)
sur un autre terminal:
ps -t pts/0
PID TTY TIME CMD
20104 pts/0 00:00:00 ksh
Donc, pas de processus enfant dans kornShell (ksh).
entre bash, il semble se comporter différemment, donné la même commande:
PID TTY TIME CMD
3458 pts/0 00:00:00 bash
20067 pts/0 00:00:00 bash
donc, un enfant processus de bash.
De lire les pages man de bash, il est évident qu'un autre processus est créé pour un sous-shell,
cependant, il faux $$, qui est sneeky.
est-ce que cette différence entre bash et ksh est attendue, ou est-ce que je lis mal les symptômes?
modifier: informations supplémentaires:
L'exécution de strace -f
sur bash et ksh sur Linux montre que bash appelle clone
deux fois pour la commande échantillon (il n'appelle pas fork
). Donc bash pourrait utiliser des threads(j'ai essayé ltrace
, mais il a été jeté!).
KornShell n'appelle ni fork
, vfork
, ni clone
.
3 réponses
ksh93 travaille très dur pour éviter les subshells. Une partie de la raison est l'évitement de stdio et l'utilisation extensive de sfio qui permet aux builtins de communiquer directement. Une autre raison est que ksh peut en théorie avoir autant de builtins. S'il est construit avec SHOPT_CMDLIB_DIR
, tous les builtins cmdlib sont inclus et activés par défaut. Je ne peux pas donner une liste complète des endroits où les cellules secondaires sont évitées, mais c'est typiquement dans les situations où seuls les builtins sont utilisés, et où il n'y a pas de redirection.
#!/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 "[0]="
else
case "${BASH_VERSINFO[*]-${!KSH_VERSION}}" in
.sh.version)
nameref v=
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 "_="
eval "_[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 "$@"
:
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 manipulation interne des e/s est que certains problèmes de buffering s'en vont. Voici un drôle d'exemple de lecture de lignes avec des builtins tee
et head
(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
dans ksh, un sous-puits peut ou non déboucher sur un nouveau procédé. Je ne sais pas quelles sont les conditions, mais le shell a été optimisé pour des performances sur des systèmes où fork()
était plus cher que sur Linux, donc il évite de créer un nouveau processus chaque fois qu'il le peut. La spécification mentionne un "nouvel environnement", mais cette séparation environnementale peut être faite en cours de processus.
une autre différence vaguement liée est l'utilisation de nouveaux procédés pour tuyau. En ksh et zsh, si la dernière commande dans un pipeline est un builtin, il fonctionne dans le processus shell actuel, donc cela fonctionne:
$ unset x
$ echo foo | read x
$ echo $x
foo
$
à bash, toutes les commandes de pipeline après la première sont exécutées en sous-couches, de sorte que 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 plus récentes de bash si vous désactivez le contrôle des tâches ( set +o monitor
) et que vous activez l'option lastpipe
( shopt -s lastpipe
). Mais ma solution habituelle est d'utiliser la substitution de processus à la place:
$ unset x
$ read x < <(echo foo)
$ echo $x
foo
La page de manuel de bash lit:
chaque commande dans un pipeline est exécutée comme un processus distinct (c.-à-d. dans une sous-cellule).
bien que cette phrase concerne les pipes, elle implique fortement qu'une sous-couche est un processus séparé.
la page de désambigui lation de Wikipedia décrit également un sous-puits en termes de processus enfant. Un processus enfant est certainement lui-même un processus.
la page de manuel de ksh (à un coup d'oeil) n'est pas directe sur sa propre définition d'un sous-puits, de sorte qu'elle n'implique pas d'une manière ou d'une autre qu'un sous-puits est un processus différent.
apprendre la coquille de Korn dit qu'ils sont des processus différents.
je dirais que vous manquez quelque chose (ou le livre est erroné ou périmé).