bash如果没有subshel​​l的话没有多个条件?


23

我想在一个if语句中合并多个条件,然后取反该组合。对于简单的条件组合,我有以下工作代码:

if [ -f file1 ] && [ -f file2 ] && [ -f file3 ] ; then
  # do stuff with the files
fi

这很好。如果要取消它,可以使用以下工作代码:

if ! ( [ -f file1 ] && [ -f file2 ] && [ -f file3 ] ) ; then
  echo "Error: You done goofed."
  exit 1
fi
# do stuff with the files

这也按预期工作。但是,发生在我身上的是我不知道括号实际上在做什么。我只想将它们用于分组,但是它实际上是在生成子shell吗?(我怎么知道?)如果是这样,有没有办法在不产生子shell的情况下对条件进行分组?


我想我也可以使用布尔逻辑来确定等价的表达式:if ! [ -f file1 ] || ! [ -f file 2 ] || ! [ -f file3 ] ; then但是我想要一个更通用的答案。
2015年

您也可以取消括号内的内容,例如if [[ ! -f file1 ]] && [[ ! -f file2 ]]; then
DopeGhoti 2015年

1
是的,我刚刚测试过if [ ! 1 -eq 2 ] && [ ! 2 -eq 3 ]; then echo yep; fi并且可以正常工作。我只是习惯性地用双花括号写测试。另外,为确保不是bash,我进一步测试了if /bin/test ! 1 -eq 2 && /bin/test ! 2 -eq 3 ; then echo yep; fi它,它也可以那样工作。
DopeGhoti 2015年

1
@Wildcard- [ ! -e file/. ] && [ -r file ]将删除目录。根据需要取反。当然是-d这样。
mikeserv

1
相关问题(相似但讨论不多,但回答不太有用):unix.stackexchange.com/q/156885/135943
通配符

Answers:


32

您需要使用{ list;}而不是(list)

if ! { [ -f file1 ] && [ -f file2 ] && [ -f file3 ]; }; then
  : do something
fi

它们都是Grouping Commands,但是{ list;}在当前的shell环境中执行命令。

请注意,需要使用;in { list;}来分隔}反向单词列表,也可以使用其他定界符。{还需要后面的空格(或其他定界符)。


谢谢!起初让我绊倒的一件事(因为我awk经常使用花括号而不是in bash),所以在打开花括号后需要空格。您提到“您也可以使用其他定界符”;有什么例子吗?
2015年

@Wildcard:是的,需要在花括号后加上空格。其他定界符的一个示例是换行符,因为您经常希望定义一个函数。
cuonglm

@don_crissti:在中zsh,您可以使用空格
cuonglm 2015年

@don_crissti:&也是分隔符,您可以使用{ echo 1;:&},也可以使用多个换行符。
cuonglm

2
您可以使用手册中的任何元字符引号they must be separated from list by whitespace or another shell metacharacter.在bash中,它们是: metacharacter: | & ; ( ) < > space tab 。对于这项特定任务,我相信任何一种& ; ) space tab都可以。.... ....来自zsh(作为评论):each sublist is terminated by ;',&', &|'&!', or a newline.

8

您可以完全使用该test功能来实现所需的功能。从手册页test

 ! expression  True if expression is false.
 expression1 -a expression2
               True if both expression1 and expression2 are true.
 expression1 -o expression2
               True if either expression1 or expression2 are true.
 (expression)  True if expression is true.

因此,您的情况可能类似于:

if [ -f file1 -a -f file2 -a -f file3 ] ; then
    # do stuff with the files
fi

对于否定,请使用转义括号:

if [ ! \( -f file1 -a -f file2 -a -f file3 \) ] ; then
    echo "Error: You done goofed."
    exit 1
fi
# do stuff with the files

6

便携式否定复杂的带壳条件,您必须应用德摩根定律并在[调用内将否定一直推到最低。

if [ ! -f file1 ] || [ ! -f file2 ] || [ ! -f file3 ]
then
    # do stuff
fi

...或者您必须使用then :; else...

if [ -f file1 ] && [ -f file2 ] && [ -f file3 ]
then :
else
  # do stuff
fi

if ! command没有可移植的,也没有[[

如果您不需要总体可移植性,请不要编写shell脚本。实际上,您比随机选择的Unix 更有可能找到/usr/bin/perlbash


1
!是POSIX,如今已足够便携。即使仍带有Bourne Shell的系统在文件系统上的其他位置也具有POSIX sh,您可以用它来解释标准语法。
斯特凡Chazelas

@StéphaneChazelas从技术上讲这是正确的,但以我的经验,用Perl重写脚本要比从定位和激活POSIX兼容外壳环境的麻烦要容易得多/bin/sh。Autoconf脚本可以按照您的建议进行操作,但是我认为我们都不太了解这种认可。
zwol

5

其他人已经注意到了{复合命令;}分组,但是如果您要在一个集合上执行相同的测试,则可能要使用另一种类型:

if  ! for f in file1 file2 file3
      do  [ -f "$f" ] || ! break
      done
then  : do stuff
fi

...如其他地方用演示的那样{ :;},嵌套复合命令没有任何困难...

请注意,以上(通常)测试常规文件。如果你正在寻找只为现有的可读的文件,这是不是目录:

if  ! for f in file1 file2 file3
      do  [ ! -d "$f" ] && 
          [   -r "$f" ] || ! break
      done
then  : do stuff
fi

如果您不在乎它们是否是目录:

if  ! command <file1 <file2 <file3
then  : do stuff
fi

...适用于任何可读的可访问的文件,但对于没有写作者的fifos可能会挂起。


2

这不是您的主要问题的完整答案,但是我注意到您在评论中提到了复合测试(可读文件)。例如,

if [ -f file1 ] && [ -r file1 ] && [ -f file2 ] && [ -r file2 ] && [ -f file3 ] && [ -r file3 ]

您可以通过定义一个shell函数来对其进行整合。例如,

readable_file()
{
    [ -f "$1" ]  &&  [ -r "$1" ]
}

将错误处理添加到(例如[ $# = 1 ])品尝。if上面的第一句话现在可以简化为

if readable_file file1  &&  readable_file file2  &&  readable_file file3

并且您可以通过缩短函数名称来进一步缩短此时间。同样,您可以定义not_readable_file()(或nrf简称为“否定”)并将否定包含在函数中。


不错,但是为什么不添加一个循环呢? readable_files() { [ $# -eq 0 ] && return 1 ; for file in "$@" ; do [ -f "$file" ] && [ -r "$file" ] || return 1 ; done ; } (免责声明:我测试了此代码,并且可以正常工作,但它不是单一代码。我在此处添加了分号,但未测试其位置。)
Wildcard

1
好点子。我会提出一个小问题,即它在函数中隐藏了更多的控制(连接)逻辑。读过脚本然后看的if readable_files file1 file2 file3人不知道该函数是在执行AND还是OR-尽管(a)我猜它很直观,(b)如果您不分发脚本,那么只要理解它就足够了, (c)因为我建议将函数名称缩写为晦涩难懂,所以我无权谈论可读性。…(续)
G-Man说“恢复莫妮卡”

1
(续)…顺便说一句,该函数的编写方式不错,但是可以替换for file in "$@" ; dofor file do它-默认为in "$@",并且(有点违反直觉)该;函数不仅不必要,而且实际上不建议使用。
G-Man说'恢复莫妮卡'

0

您也可以在括号测试中取反,以便重复使用原始代码:

if [[ ! -f file1 ]] || [[ ! -f file2 ]] || [[ ! -f file3 ]] ; then
  # do stuff with the files
fi

2
您需要的||不是&&
cuonglm 2015年

该测试是不是“有任何文件不存在的”,测试“是没有文件的存在”。||如果不存在任何文件,并且仅当不存在所有文件时,使用会执行谓词。NOT(A AND B AND C)与(NOT A)AND(NOT B)AND(NOT C)相同;看到原来的问题。
DopeGhoti 2015年

@ DopeGhoti,cuonglm是对的。如果不是(所有文件都在那里),那么当您以这种方式将其拆分时,您需要的||不是&&。请参阅我的问题本身下方的第一条评论。
通配符2015年

2
@DopeGhoti:not (a and b)与相同(not a) or (not b)。参见en.wikipedia.org/wiki/De_Morgan%27s_laws
cuonglm

1
一定是星期四。我永远都无法摆脱周四的麻烦。已更正,我将口口相传。
DopeGhoti

-1

我只想将它们用于分组,但是它实际上是在生成子shell吗?(我怎么知道?)

是的,中的命令在(...)子外壳中运行。

要测试您是否在子外壳中,可以将当前外壳的PID与交互式外壳的PID进行比较。

echo $BASHPID; (echo $BASHPID); 

输出示例,表明括号产生了子外壳,花括号不产生:

$ echo $BASHPID; (echo $BASHPID); { echo $BASHPID;}
50827
50843
50827
$ 

6
()会生成subshel​​l {}..
也许

@heemayl,这是我的问题的另一部分-是否可以通过某种方式在屏幕上看到演示产生子外壳的事实?(这确实可能是一个单独的问题,但在这里知道是个好消息。)
Wildcard

1
@heemayl你是对的!我已经做过echo $$ && ( echo $$ )比较PID的操作,它们是相同的,但是在我提交评论告诉您您错了之前,我尝试了另一件事。 echo $BASHPID && ( echo $BASHPID )给出了不同的PID
David King

1
@heemayl echo $BASHPID && { echo $BASHPID; }提供与您建议的情况相同的PID。感谢您的接受教育
大卫·金

@DavidKing,感谢您使用的测试命令$BASHPID,这是我丢失的另一件事。
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.