我理解,因为|
在管道之后启动了命令的新进程,所以cmd | cd newdir
形式的任何shell命令(其中cmd
}不会更改当前工作目录)将保持原始进程的工作目录不变。 (更不用说这有点傻了,因为cd
没有从stdin读取输入。)
但是,在我的机器上(使用bash
,ksh
或zsh
的CentOS 6机箱),似乎以下命令也失败更改目录:
cd newdir | cat
(请忽略在这里将输出输出到cat是多么愚蠢;我只是想做一个简单的例子。)
这是为什么?有没有解决这个问题的方法?具体来说,我正在尝试编写一个使用popd
的别名,但会捕获输出,丢弃标准输出并重新输出stderr。
(好奇的是,这是我当前的非工作别名:popd 2>&1 >/dev/null | toerr && lsd
。这里,toerr
只捕获stdin,将其输出到stderr,并返回读取/打印的行数。 lsd
是一个目录名和内容打印机,只有在popd
成功时才能执行。我将stderr发送到stdout,管道,捕获它并重新输出它的原因在stderr上只是使用stderred将其变为红色,因为我的shell会话未加载LD_PRELOAD,因此诸如popd
之类的Bash内置函数不会获得红色的stderr。)
答案 0 :(得分:2)
在bash
,dash
和ash
中,管道中的每个命令都在子shell中运行。
在zsh
,ksh
和bash
以及shopt -s lastpipe
中,除管道中的最后一个命令外,所有命令都在子shell中运行。
由于cd
- 以及变量,shell选项,ulimits和新文件描述符 - 仅影响当前进程,因此它们的效果不会影响父shell。
示例:
# Doesn't change directory
cd foo | cat
pwd
# Doesn't set $bar on default bash (but does on zsh and ksh)
echo foo | read bar
echo "$bar"
# Doesn't change the ulimit
ulimit -c 10000 2>&1 | grep "not permitted"
ulimit -c
同样也适用于产生子壳的其他东西。以下任何内容都不会更改目录:
# Command expansion creates a subshell
echo $(cd foo); pwd
# ( .. ) creates a subshell
( cd foo ); pwd
# Backgrounding a process creates a subshell
cd foo & pwd
要修复它,您必须重写代码以运行影响主shell进程中环境的任何内容。
在您的特定情况下,您可以考虑使用流程替换:
popd > /dev/null 2> >(toerr) && lsd
这样做的另一个好处是,只有在lsd
成功时运行popd
,而不是像您的版本那样成功toerr
。