我在我的bash函数中使用"exit 1"语句来终止整个脚本,它工作正常:
function func()
{
echo "Goodbye"
exit 1
}
echo "Function call will abort"
func
echo "This will never be printed"
Run Code Online (Sandbox Code Playgroud)
但后来我意识到,当调用它时它不起作用:
res=$(func)
Run Code Online (Sandbox Code Playgroud)
据我所知,我创建了一个子shell,"退出1"中止了子shell而不是主要的....
但有没有办法编写一个中止整个执行的函数,无论它如何被调用?我只需要获得真正的返回值(由函数回应).
如何将多个命令的stdout传递给单个命令?
示例1:组合并排序所有三个echo命令的输出:
echo zzz; echo aaa; echo kkk
Run Code Online (Sandbox Code Playgroud)
期望的输出:
aaa
kkk
zzz
Run Code Online (Sandbox Code Playgroud)
示例2:重写以下内容,以便所有命令都使用管道在单个命令行中,而不重定向到临时文件:
setopt > /tmp/foo; unsetopt >> /tmp/foo; set >> /tmp/foo; sort /tmp/foo
Run Code Online (Sandbox Code Playgroud) 昨天有人向我建议在bash中使用命令替换会导致产生不必要的子shell.该建议仅针对此用例:
# Extra subshell spawned
foo=$(command; echo $?)
# No extra subshell
command
foo=$?
Run Code Online (Sandbox Code Playgroud)
我认为这对于这个用例似乎是正确的.但是,快速搜索试图验证这会导致大量令人困惑和矛盾的建议.似乎流行的智慧说所有命令替换的使用都会产生一个子shell.例如:
命令替换扩展为命令的输出.这些命令在子shell中执行,其stdout数据是替换语法扩展的内容.(来源)
这看起来很简单,除非你继续挖掘,在这种情况下你会开始找到建议的参考,但事实并非如此.
命令替换不一定会调用子shell,在大多数情况下也不会.它唯一保证的是乱序评估:它只是首先评估替换中的表达式,然后使用替换结果评估周围的语句.(来源)
这似乎是合理的,但这是真的吗?这个与子shell相关的问题的回答让我觉得有点man bash
需要注意:
管道中的每个命令都作为单独的进程执行(即,在子shell中).
这让我想到了主要问题.究竟是什么导致命令替换产生一个副本,这个子shell无论如何都不会被隔离地执行相同的命令?
请考虑以下情况并解释哪些情况会产生额外子shell的开销:
# Case #1
command1
var=$(command1)
# Case #2
command1 | command2
var=$(command1 | command2)
# Case #3
command1 | command 2 ; var=$?
var=$(command1 | command2 ; echo $?)
Run Code Online (Sandbox Code Playgroud)
这些对中的每一对都会产生相同数量的子壳来执行吗?POSIX与bash实现有区别吗?是否有其他情况下使用命令替换会产生一个子shell,在这种情况下运行同一组命令不会?
一个人为的例子......给出了
FOO="/foo/bar/baz"
Run Code Online (Sandbox Code Playgroud)
这工作(在bash中)
BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1} # result is BAZ="b"
Run Code Online (Sandbox Code Playgroud)
这不
BAZ=${$(basename $FOO):0:1} # result is bad substitution
Run Code Online (Sandbox Code Playgroud)
我的问题是哪个规则导致这个[子shell替换]评估不正确?如果有的话,在1跳中执行此操作的正确方法是什么?
我有一个bash脚本start.sh,如下所示:
for thing in foo bar; do
{
background_processor $thing
cleanup_on_exit $thing
} &
done
Run Code Online (Sandbox Code Playgroud)
这就是我想要的:我运行start.sh,它以代码0退出,并且两个子shell在后台运行.每个子shell运行background_processor
,当它退出时,它运行cleanup_on_exit
.即使我退出我最初运行start.sh的终端(即使这是一个ssh连接),这也有效.
然后我尝试了这个:
ssh user@host "start.sh"
Run Code Online (Sandbox Code Playgroud)
这是有效的,除了start.sh
退出后,ssh显然也等待子壳退出.我真的不明白为什么.一旦start.sh
退出,子shell变成pid 1的子项,并且它们甚至没有被赋予tty ...所以我无法理解它们如何仍然与我的ssh连接相关联.
我后来试过这个:
ssh -t user@host "start.sh"
Run Code Online (Sandbox Code Playgroud)
现在进程有一个指定的伪tty.现在,我发现ssh一旦start.sh
退出就会退出,但它也会杀死子进程.
我猜想在后一种情况下子进程被发送了SIGHUP,所以我这样做了:
ssh -t user@host "nohup start.sh"
Run Code Online (Sandbox Code Playgroud)
这实际上有效!所以,我有一个解决我的实际问题的方法,但我想在这里掌握SIGHUP/tty的微妙之处.
总之,我的问题是:
start.sh
退出后也等待子进程,即使它们有父pid 1?ksh有一个非常有趣的构造来做到这一点,详细解答如下:https://stackoverflow.com/a/11172617/636849
从Bash 4.0开始,有一个内置的mapfile内置命令可以解决这个问题:http: //www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html
但奇怪的是,它似乎不适用于进程替换:
foo () { echo ${BASH_SUBSHELL}; }
mapfile -t foo_output <(foo) # FAIL: hang forever here
subshell_depth=${foo_output[0]} # should be 0
Run Code Online (Sandbox Code Playgroud)
但是如何在Bash v3.2中做到这一点?
我在版本2.32.2的管道中使用Jenkins文件.
由于各种原因,我想从pom中提取版本字符串.我希望我不必添加maven帮助插件并使用evaluate.
我很快想出了一个小的sed表达式,将它从使用管道的pom中取出,并在执行程序的jenkins工作区中的命令行上运行.
$ sed -n '/<version>/,/<version/p' pom.xml | head -1 | sed 's/[[:blank:]]*<\/*version>//g'
1.0.0-SNAPSHOT
它可能会被优化,但我想理解为什么管道似乎在管道sh命令失败.我玩过各种字符串格式,目前正在使用一个美元的字符串.
管道步骤如下所示,以便轻松输出命令字符串:
script {
def ver_script = $/sed -n '/<version>/,/<version/p' pom.xml | head -1 | sed 's/[[:blank:]]*<\/*version>//g'/$
echo "${ver_script}"
POM_VERSION = sh(script: "${ver_script}", returnStdout: true)
echo "${POM_VERSION}"
}
Run Code Online (Sandbox Code Playgroud)
当在jenkins管道中运行时,我得到以下控制台输出,它似乎将管道命令分离为单独的命令:
[Pipeline] script
[Pipeline] {
[Pipeline] echo
sed -n '/<version>/,/<version/p' pom.xml | head -1 | sed 's/[[:blank:]]*<\/*version>//g'
[Pipeline] sh
[FRA-198-versioned-artifacts-44SD6DBQOGOI54UEF7NYE4ECARE7RMF7VQYXDPBVFOHS5CMSTFLA] Running shell script
+ sed -n /<version>/,/<version/p pom.xml
+ head -1
+ sed s/[[:blank:]]*<\/*version>//g
sed: couldn't write …
Run Code Online (Sandbox Code Playgroud) 如何wget
从子shell进程获取退出代码?
所以,主要问题是$?
等于0.哪里可以$?=8
建立?
$> OUT=$( wget -q "http://budueba.com/net" | tee -a "file.txt" ); echo "$?"
0
Run Code Online (Sandbox Code Playgroud)
tee
实际上它没有用.
$> OUT=$( wget -q "http://budueba.com/net" ); echo "$?"
8
Run Code Online (Sandbox Code Playgroud)
但是${PIPESTATUS}
数组(我不确定它与那种情况有关)也不包含该值.
$> OUT=$( wget -q "http://budueba.com/net" | tee -a "file.txt" ); echo "${PIPESTATUS[1]}"
$> OUT=$( wget -q "http://budueba.com/net" | tee -a "file.txt" ); echo "${PIPESTATUS[0]}"
0
$> OUT=$( wget -q "http://budueba.com/net" | tee -a "file.txt" ); echo "${PIPESTATUS[-1]}"
0
Run Code Online (Sandbox Code Playgroud)
所以,我的问题是 - 如何wget
通过tee
子shell …
我已经看到很多关于Stack Overflow的答案和评论,提到做一些事情来避免子shell.在某些情况下,给出了一个功能上的原因(通常,可能需要读取在其中分配的子shell之外的变量),但在其他情况下,避免似乎本身就被视为目的.例如
tsv文件的两列联合
建议{ ... ; } | ...
而不是
( ... ) | ...
,所以有两种方式的子shell.
Linux bash脚本
明确地复制文件,"目标只是避免子shell"
为什么是这样?是为了风格/优雅/美丽?为了表现(避免分叉)?为了防止可能的错误?别的什么?