サブシェルなしで複数の条件でない場合はbash?


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

これも期待どおりに機能します。しかし、括弧が実際に何をしているのかわからないことがわかりました。私がしたいだけのグループ化のためにそれらを使用するが、それは実際にサブシェルを産卵されましたか?(どうすればわかりますか?)その場合、サブシェルを生成せずに条件をグループ化する方法はありますか?


ブールロジックを使用して同等の式を決定することもできますif ! [ -f file1 ] || ! [ -f file 2 ] || ! [ -f file3 ] ; thenが、より一般的な答えが必要です。
ワイルドカード

あなたはすることもでき、中括弧、例えば内部否定if [[ ! -f file1 ]] && [[ ! -f file2 ]]; then
DopeGhoti

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

1
@Wildcard- [ ! -e file/. ] && [ -r file ]ディレクトリをドロップします。あなたが好きなようにそれを否定します。もちろん、そういうこと-dです。
mikeserv

1
(似ていますが、あまり議論と回答それほど役に立たないなど)関連の質問:unix.stackexchange.com/q/156885/135943
ワイルドカード

回答:


32

次の{ list;}代わりに使用する必要があります(list)

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

どちらもGrouping Commandsですが{ list;}、現在のシェル環境でコマンドを実行します。

リストを逆ワードから区切る;{ list;}はin が必要}ですが、他の区切り文字も使用できることに注意してください。後のスペース(または他の区切り文字){も必要です。


ありがとうございました!最初に私をつまずかせたのは(中括弧awkよりも中括弧を頻繁に使用bashするため)、開いた中括弧の後の空白の必要性です。「他の区切り文字も使用できます」と言っています。例はありますか?
ワイルドカード

@Wildcard:はい、開き中括弧の後の空白が必要です。他の区切り文字の例は改行です。多くの場合、関数を定義する必要があります。
クオンルム

@don_crissti:ではzsh、あなたは空白を使用することができます
cuonglm

@don_crissti:は&セパレータでもあり、使用できます{ echo 1;:&}。複数の改行も使用できます。
クオンルム

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機能を完全に使用して、目的を達成できます。のmanページから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

シェルの複雑な条件を移植可能に否定するには、De Morganの法則を適用し、[呼び出し内で否定を完全に押し下げる必要があります...

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は移植性がなく、どちらも利用できません[[

完全な移植性が必要ない場合は、シェルスクリプトを記述しないください。あなたが実際にしているより見つけることはそう/usr/bin/perlあなたがよりランダムに選択されたUnix上でbash


1
!POSIXであり、今日では十分に移植可能です。まだBourneシェルが付属しているシステムでも、ファイルシステム上のどこかにPOSIX shがあり、これを使用して標準構文を解釈できます。
ステファンシャゼル

@StéphaneChazelasこれは技術的には正しいのですが、私の経験では、から始まるPOSIX準拠のシェル環境を見つけてアクティブにするという面倒なことに対処するよりも、Perlでスクリプトを書き直す方が簡単/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

... 読み取り可能アクセス可能なファイルであれば機能しますが、ライターなしのfifoではハングする可能性があります。


2

これは主な質問に対する完全な答えではありませんが、コメントで複合テスト(読み取り可能なファイル)に言及していることに気付きました。例えば、

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

シェル関数を定義することにより、これを少し統合できます。例えば、

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 ; } (免責事項:このコードをテストし、動作しますが、1行ではありません。ここに投稿するためにセミコロンを追加しましたが、配置をテストしませんでした。)
ワイルドカード

1
いい視点ね。関数内の制御(接続)ロジックの多くを隠すという小さな懸念を提起します。スクリプトを読み取って、見ている誰かif readable_files file1 file2 file3ではないだろう知っている機能がやっていたかどうかをANDOR、スクリプトを配布していない場合は()私は(b)に、それはかなり直感的だと思うがあれば、それは良い十分です- あなたはそれを理解し、 (c)関数名を不明瞭にすることを提案したため、読みやすさについて話す立場にありません。…(続き)
Gマンは「Reinstate Monica」と言います

1
(続きは)...ところで、この関数は罰金、あなたがそれを書いた方法ですが、あなたは置き換えることができfor file in "$@" ; dofor file do、それがデフォルトに- in "$@"(やや反直感的に)、および;不必要な実際に落胆だけではありません。
G-Manは「Reinstate Monica」と言います

0

ブレーステスト内で無効にすることもできるため、元のコードを再利用するには:

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

2
あなたは必要とする||代わりに&&
cuonglm

テストは、「あるではありません任意のファイルのがない」、テストは「ないですどれもファイルの存在」。使用し||ている場合、述語を実行します任意のファイルのが存在しないとだけ場合に限っていない場合、すべてのファイルの存在しません。NOT(A AND B AND C)は(NOT A)AND(NOT B)AND(NOT C)と同じです。元の質問を参照してください。
DopeGhoti

@DopeGhoti、クオンルムは正しい。そうでない場合(すべてのファイルが存在するため)、この方法で分割する場合はの||代わりに必要です&&。質問自体の下にある最初のコメントを参照してください。
ワイルドカード

2
@DopeGhoti:はとnot (a and b)同じ(not a) or (not b)です。参照してくださいen.wikipedia.org/wiki/De_Morgan%27s_laws
cuonglm

1
木曜日でなければなりません。私は木曜日のこつを得ることができませんでした。修正しました。後世のために口を口にしておきます。
DopeGhoti

-1

グループ化だけに使用したいのですが、実際にはサブシェルが生成されますか?(どうすればわかりますか?)

はい、コマンド(...)はサブシェルで実行されます。

サブシェルにいるかどうかをテストするには、現在のシェルのPIDと対話型シェルのPIDを比較できます。

echo $BASHPID; (echo $BASHPID); 

括弧がサブシェルを生成し、中括弧が生成しないことを示す出力例:

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

6
()あなたは意味のスポーンsubshel​​l..perhapsん{}....
heemayl

@heemayl、それは私の質問の別の部分です。サブシェルが生成されているという事実を画面上で確認または実証する方法はありますか?(それは本当に別の質問かもしれませんが、ここで知っておくといいでしょう。)
ワイルドカード

1
@heemaylその通りです!私はecho $$ && ( echo $$ )PIDを比較しましたが、それらは同じでしたが、あなたが間違っているとあなたに言っているコメントを提出する直前に、私はもう1つのことを試みました。 echo $BASHPID && ( echo $BASHPID )さまざまなPIDを与える
デビッドキング

1
@heemayl echo $BASHPID && { echo $BASHPID; }は、あなたが提案したのと同じPIDを与えます。教育をありがとう
デビッドキング

@DavidKing、を使用したテストコマンドに感謝します$BASHPID
ワイルドカード
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.