如果命令失败,则在回显后退出_始终_退出

3
我有一个Bash脚本设置,其中我使用eval变量称为snake,并在出现错误时退出。即使执行snake命令时没有错误,下面的aws s3命令也不会执行。如果我删除|| echo "ERROR OCCURED, LOOK ABOVE. EXITING" ; exit 1,则aws命令将执行。我知道在eval $snake时没有错误,因为没有返回echo "ERROR OCCURED, LOOK ABOVE. EXITING"到标准输出(只有在实际发生错误时才会返回)。我需要在成功运行eval $snake后执行aws命令,但我不确定如何做到这一点。
s3='ebio/'

# do not edit contents below unless needed

snake="snakemake --default-remote-provider S3 --default-remote-prefix '$s3' --use-conda --cores 32 --rerun-incomplete --printshellcmds"

read -p "Rewrite over samples.tsv, peak_norm.tsv, and treated_vs_untreated.tsv files? (y/n)" -n 1 -r

if [[ $REPLY =~ ^[Yy]$ ]]
then        

# exit if snake fails

eval $snake || echo "ERROR OCCURED, LOOK ABOVE. EXITING" ; exit 1
    
# remove temp files
        
aws s3 rm --recursive s3://"$s3"rep_element_pipeline --exclude "*" --include "*tmp"
aws s3 rm --recursive s3://"$s3"rep_element_pipeline --exclude "*" --include "*sam.parsed"
aws s3 rm --recursive s3://"$s3"rep_element_pipeline --exclude "*" --include "*sam.parsed.done"
aws s3 rm --recursive s3://"$s3"rep_element_pipeline --exclude "*" --include "*.sam.tmp.combined_w_uniquemap.rmDup.sam"
aws s3 rm --recursive s3://"$s3"rep_element_pipeline --exclude "*" --include "*.sam.tmp.combined_w_uniquemap.prermDup.sam"
aws s3 rm --recursive s3://"$s3"rep_element_pipeline --exclude "*" --include "*.adapterTrim.round2.rmRep.bam.tmp"
aws s3 rm --recursive s3://"$s3"rep_element_pipeline --exclude "*" --include "*.fastq.gz.mapped_vs_bt2_hg38.sam"
aws s3 rm --recursive s3://"$s3"data_analysis --exclude "*" --include "*.saturations_reads.txt"

fi

3
如果你希望只有在||左侧的语句返回false时才会执行exit,那么你需要使用一个组,例如|| { echo "whatever"; exit 1; } - Charles Duffy
3
另外,**不要使用eval**。请参见BashFAQ#48BashFAQ#50 - Charles Duffy
2
谈到为什么在eval字符串中使用'$s3'是危险的,考虑一下如果s3=$'$(rm -rf ~)\'$(rm -rf ~)\''会发生什么(请注意,该字符串中的每个字符都是UNIX文件名中有效的,因此您不能安全地将任意名称的字符串替换为它们)。 - Charles Duffy
1个回答

3
这里的直接问题是,在foo || bar; baz中,无论foo的退出状态是什么,都会执行baz。在这方面,作为命令分隔符的分号和换行符的处理方式是完全相同的。可以通过显式分组来解决这个问题,例如foo || { bar; baz; } ——或者更直观地写成if foo; then bar; baz; fi
更好的编码方式应该是:
# multi-line form optional, but lets you add comments after each line
snake=(
  snakemake
  --default-remote-provider S3
  --default-remote-prefix '$s3'  # FIXME: Sure you don't want "$s3" instead?
  --use-conda
  --cores 32
  --rerun-incomplete
  --printshellcmds
)
"${snake[@]}" || { echo "ERROR OCCURRED, LOOK ABOVE, EXITING" >&2; exit 1; }

将命令存储在数组或函数中,而不是字符串中,可以避免使用eval及其带来的安全问题; 这些技术都在BashFAQ#50中进行了教学,有关eval的问题在BashFAQ#48中详细介绍。

使用花括号将exit 1放在与echo相同的组中,确保它们同时发生或者根本不发生。

请注意,'$s3'仅在您希望将确切的字符串$s3作为默认远程前缀传递时才是正确的;如果您希望使用名为s3的变量的内容作为默认远程前缀,请将其更改为"$s3"


1
更好的方法是跳过变量,直接定义一个函数。 - chepner
1
我已经点赞了,但是在我看来,你应该更加强调指出他的错误(即无条件的 exit 1),然后再着重于改进他的代码。 - Super-intelligent Shade
1
@InnocentBystander,说得好;我已经编辑了一个狭窄的第一段。 - Charles Duffy
1
我喜欢添加一个发出错误并退出的函数:die() { echo "$*" >&2; exit 1; }然后“or”子句只是 "${snake[@]}" || die "Error occurred ..." - glenn jackman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接