bash和ksh之间的子shell差异

cda*_*rke 7 linux bash shell scripting ksh

我一直认为子shell不是子进程,而是同一进程中的另一个shell环境.

我使用一组基本的内置函数:

(echo "Hello";read)
Run Code Online (Sandbox Code Playgroud)

在另一个终端:

ps -t pts/0
  PID TTY          TIME CMD
20104 pts/0    00:00:00 ksh
Run Code Online (Sandbox Code Playgroud)

所以,kornShell(ksh)中没有子进程.

输入bash,看起来行为不同,给出相同的命令:

  PID TTY          TIME CMD
 3458 pts/0    00:00:00 bash
20067 pts/0    00:00:00 bash
Run Code Online (Sandbox Code Playgroud)

所以,bash中的子进程.
从阅读bash的手册页来看,显然是为子shell创建了另一个进程,但是它伪造了$$,这是一个偷偷摸摸的.

bash和ksh之间是否存在这种差异,或者我是否正确地阅读了这些症状?

编辑:附加信息:strace -f在Linux上运行bash和ksh显示bash clone为sample命令调用两次(它不调用fork).所以bash可能正在使用线程(我试过,ltrace但核心转储!).KornShell既不叫fork,vfork也不clone.

Mar*_*eed 12

在ksh中,子shell可能会也可能不会导致新进程.我不知道条件是什么,但是shell在针对fork()比Linux上通常更昂贵的系统上的性能进行了优化,因此它可以避免在任何时候创建新进程.规范说"新环境",但环境分离可以在进程中完成.

另一个模糊相关的区别是管道的新工艺的使用.在ksh和zsh中,如果管道中的最后一个命令是内置命令,它将在当前shell进程中运行,因此这有效:

$ unset x
$ echo foo | read x
$ echo $x
foo
$
Run Code Online (Sandbox Code Playgroud)

在bash中,第一个之后的所有管道命令都在子shell中运行,因此上面的命令不起作用:

$ unset x
$ echo foo | read x
$ echo $x

$
Run Code Online (Sandbox Code Playgroud)

正如@ dave-thompson-085指出的那样,如果你关闭作业控制(set +o monitor)并打开lastpipe选项(shopt -s lastpipe),你可以在bash版本4.2和更新版本中获得ksh/zsh行为.但我通常的解决方案是使用进程替换:

$ unset x
$ read x < <(echo foo)
$ echo $x
foo
Run Code Online (Sandbox Code Playgroud)


orm*_*aaj 9

ksh93非常难以避免使用子壳.部分原因是避免使用stdio和广泛使用sfio,这允许内置程序直接通信.另一个原因是ksh在理论上可以有这么多内置.如果使用SHOPT_CMDLIB_DIR,则默认包含并启用所有cmdlib内置函数.我不能给出避免子壳的地方的完整列表,但通常只在使用内置的情况下,并且没有重定向的情况下.

#!/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 "$@"
Run Code Online (Sandbox Code Playgroud)

出:

Version AJM 93v- 2013-02-22
31732 31732
31735 31732
31736 31732 
31732 31732 
31732 31732
31732 31732 
31732 31732
31738 31732
Run Code Online (Sandbox Code Playgroud)

所有这些内部I/O处理的另一个巧妙结果是一些缓冲问题就会消失.这是一个有趣的使用teehead内置读取行的例子(不要在任何其他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
Run Code Online (Sandbox Code Playgroud)