bash jika tidak banyak kondisi tanpa subkulit?


23

Saya ingin menggabungkan beberapa kondisi dalam pernyataan if shell, dan meniadakan kombinasi. Saya memiliki kode kerja berikut untuk kombinasi kondisi sederhana:

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

Ini berfungsi dengan baik. Jika saya ingin meniadakannya, saya dapat menggunakan kode kerja berikut:

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

Ini juga berfungsi seperti yang diharapkan. Namun, terpikir olehku bahwa aku tidak tahu apa yang sebenarnya dilakukan kurung di sana. Saya ingin menggunakannya hanya untuk pengelompokan, tetapi apakah ini benar-benar menghasilkan subkulit? (Bagaimana saya bisa tahu?) Jika demikian, apakah ada cara untuk mengelompokkan kondisi tanpa menelurkan subkulit?


Saya kira saya juga bisa menggunakan logika boolean untuk menentukan ekspresi yang setara: if ! [ -f file1 ] || ! [ -f file 2 ] || ! [ -f file3 ] ; thentapi saya ingin jawaban yang lebih umum.
Wildcard

Anda juga dapat meniadakan di dalam kawat gigi, misalnyaif [[ ! -f file1 ]] && [[ ! -f file2 ]]; then
DopeGhoti

1
Ya, saya baru menguji if [ ! 1 -eq 2 ] && [ ! 2 -eq 3 ]; then echo yep; fidan berhasil. Saya selalu menulis tes dengan kawat gigi ganda sebagai kebiasaan. Juga, untuk memastikannya tidak bash, saya diuji lebih lanjut if /bin/test ! 1 -eq 2 && /bin/test ! 2 -eq 3 ; then echo yep; fidan berfungsi seperti itu juga.
DopeGhoti

1
@ Kartu Memori - [ ! -e file/. ] && [ -r file ]akan menjatuhkan direktori. Meniadakannya sesuka Anda. tentu saja, itulah yang -ddilakukannya.
mikeserv

1
Pertanyaan terkait (mirip tetapi tidak banyak diskusi dan jawaban tidak begitu membantu): unix.stackexchange.com/q/156885/135943
Wildcard

Jawaban:


32

Anda harus menggunakan { list;}alih-alih (list):

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

Keduanya adalah Perintah Pengelompokan , tetapi { list;}mengeksekusi perintah di lingkungan shell saat ini.

Perhatikan bahwa, ;dalam { list;}diperlukan untuk membatasi daftar dari }kata terbalik, Anda dapat menggunakan pembatas lainnya juga. Ruang (atau pembatas lainnya) setelahnya {juga diperlukan.


Terima kasih! Sesuatu yang membuat saya tersandung pada awalnya (karena saya awklebih sering menggunakan kurung kurawal daripada di bash) adalah kebutuhan untuk spasi putih setelah kurawal kurawal terbuka. Anda menyebutkan "Anda dapat menggunakan pembatas lain juga"; ada contoh?
Wildcard

@ Kartu Memori: Ya, spasi putih setelah kurung kurawal terbuka diperlukan. Contoh untuk pembatas lainnya adalah baris baru, karena Anda sering ingin mendefinisikan suatu fungsi.
cuonglm

@don_crissti: Dalam zsh, Anda dapat menggunakan spasi putih
cuonglm

@don_crissti: &ini juga merupakan pemisah, Anda dapat menggunakan { echo 1;:&}, beberapa baris juga dapat digunakan.
cuonglm

2
Anda dapat menggunakan kutipan metakarakter dari manual they must be separated from list by whitespace or another shell metacharacter.Dalam pesta mereka: metacharacter: | & ; ( ) < > space tab . Untuk tugas khusus ini, saya yakin semua & ; ) space tabakan bekerja. .... .... Dari zsh (sebagai komentar) each sublist is terminated by &', &!', or a newline.

8

Anda dapat menggunakan testfungsionalitas sepenuhnya untuk mencapai apa yang Anda inginkan. Dari halaman manual dari 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.

Jadi kondisi Anda dapat terlihat seperti:

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

Untuk meniadakan penggunaan tanda kurung lolos:

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

6

Untuk portable meniadakan bersyarat kompleks dalam shell, Anda harus baik menerapkan hukum De Morgan dan mendorong negasi semua jalan ke bawah dalam [panggilan ...

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

... atau Anda harus menggunakan then :; else...

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

if ! commandtidak tersedia dan tidak mudah dibawa [[.

Jika Anda tidak membutuhkan portabilitas total, jangan tulis skrip shell . Anda sebenarnya lebih mungkin menemukan /usr/bin/perlUnix yang dipilih secara acak daripada Anda bash.


1
!adalah POSIX yang saat ini cukup portabel. Bahkan sistem yang masih menggunakan shell Bourne juga memiliki POSIX sh di tempat lain pada sistem file yang dapat Anda gunakan untuk menginterpretasikan sintaks standar Anda.
Stéphane Chazelas

@ StéphaneChazelas Ini secara teknis benar tetapi dalam pengalaman saya lebih mudah untuk menulis ulang skrip di Perl daripada berurusan dengan kerumitan mencari dan mengaktifkan lingkungan shell yang sesuai dengan POSIX mulai dari /bin/sh. Skrip autoconf melakukan seperti yang Anda sarankan, tapi saya pikir kita semua tahu betapa sedikitnya dukungan yang ada.
zwol

5

yang lain telah mencatat pengelompokan {perintah majemuk ;}, tetapi jika Anda melakukan tes yang identik pada set Anda mungkin ingin menggunakan jenis yang berbeda:

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

... seperti yang ditunjukkan di tempat lain { :;}, tidak ada kesulitan yang terlibat dengan perintah gabungan bersarang ...

Perhatikan bahwa tes di atas (biasanya) untuk file biasa . Jika Anda hanya mencari file yang ada dan dapat dibaca yang bukan merupakan direktori:

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

Jika Anda tidak peduli apakah itu direktori atau tidak:

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

... berfungsi untuk semua file yang dapat dibaca dan diakses , tetapi kemungkinan akan hang untuk fifos tanpa penulis.


2

Ini bukan jawaban penuh untuk pertanyaan utama Anda, tetapi saya perhatikan bahwa Anda menyebutkan pengujian gabungan (file yang dapat dibaca) dalam komentar; misalnya,

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

Anda dapat menggabungkan ini sedikit dengan mendefinisikan fungsi shell; misalnya,

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

Tambahkan penanganan kesalahan ke (misalnya, [ $# = 1 ]) secukupnya. ifPernyataan pertama , di atas, sekarang dapat diringkas menjadi

if readable_file file1  &&  readable_file file2  &&  readable_file file3

dan Anda dapat mempersingkat ini lebih jauh dengan mempersingkat nama fungsi. Demikian juga, Anda bisa mendefinisikan not_readable_file()(atau nrfsingkatnya) dan memasukkan negasi dalam fungsi.


Bagus, tapi mengapa tidak menambahkan satu lingkaran saja? readable_files() { [ $# -eq 0 ] && return 1 ; for file in "$@" ; do [ -f "$file" ] && [ -r "$file" ] || return 1 ; done ; } (Penafian: Saya menguji kode ini dan berfungsi tetapi itu bukan satu-liner. Saya menambahkan tanda titik koma untuk posting di sini tetapi tidak menguji penempatan mereka.)
Wildcard

1
Poin bagus. Saya akan menyampaikan keprihatinan kecil bahwa ia lebih banyak menyembunyikan kontrol (kata hubung) logika dalam fungsi; seseorang yang membaca skrip dan melihat if readable_files file1 file2 file3tidak akan tahu apakah fungsi itu melakukan DAN atau ATAU - meskipun (a) saya kira itu cukup intuitif, (b) jika Anda tidak mendistribusikan skrip, itu cukup baik jika Anda memahaminya, dan (c) karena saya menyarankan menyingkat nama fungsi menjadi ketidakjelasan, saya tidak dapat berbicara tentang keterbacaan. … (Lanjutan)
G-Man Mengatakan 'Reinstate Monica'

1
(Lanjutkan) ... BTW, fungsinya baik-baik saja dengan cara Anda menulisnya, tetapi Anda dapat menggantinya for file in "$@" ; dodengan for file do- defaultnya adalah in "$@", dan (agak kontra-intuitif) ;tidak hanya tidak perlu tetapi sebenarnya tidak disarankan.
G-Man Mengatakan 'Reinstate Monica'

0

Anda dapat meniadakan di dalam tes brace juga, jadi untuk menggunakan kembali kode asli Anda:

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

2
Anda perlu ||bukannya&&
cuonglm

Tes tidak "apakah ada file yang tidak ada", tes ini "tidak ada file di sana". Menggunakan ||akan mengeksekusi predikat jika ada file tidak ada, tidak jika dan hanya jika semua file tidak ada. BUKAN (A DAN B DAN C) sama dengan (BUKAN A) DAN (BUKAN B) DAN (BUKAN C); lihat pertanyaan aslinya.
DopeGhoti

@DopeGhoti, cuonglm benar. Ini jika tidak (semua file yang ada) sehingga ketika Anda membaginya dengan cara ini Anda perlu ||bukan &&. Lihat komentar pertama saya di bawah pertanyaan saya sendiri.
Wildcard

2
@DopeGhoti: not (a and b)sama dengan (not a) or (not b). Lihat en.wikipedia.org/wiki/De_Morgan%27s_laws
cuonglm

1
Itu pasti hari Kamis. Saya tidak pernah bisa memahami hari Kamis. Diperbaiki, dan aku akan meninggalkan kaki-ke-mulutku untuk anak cucu.
DopeGhoti

-1

Saya ingin menggunakannya hanya untuk pengelompokan, tetapi apakah ini sebenarnya memunculkan subkulit? (Bagaimana saya bisa tahu?)

Ya, perintah di dalam (...)dijalankan dalam subkulit.

Untuk menguji apakah Anda berada dalam subkulit Anda dapat membandingkan PID dari shell Anda saat ini dengan PID dari shell interaktif.

echo $BASHPID; (echo $BASHPID); 

Contoh output, menunjukkan bahwa kurung menelurkan subkulit dan kurung kurawal tidak:

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

6
()tidak menelurkan subkulit..mungkin {}
maksudmu

@ heemayl, itu bagian lain dari pertanyaan saya — apakah ada cara saya bisa melihat atau menunjukkan pada layar saya fakta tentang subkulit yang muncul? (Itu benar-benar bisa menjadi pertanyaan terpisah tetapi akan baik untuk diketahui di sini.)
Wildcard

1
@ heemayl Anda benar! Saya telah selesai echo $$ && ( echo $$ )membandingkan PID dan semuanya sama, tetapi tepat sebelum saya mengirimkan komentar yang mengatakan Anda salah, saya mencoba satu hal lagi. echo $BASHPID && ( echo $BASHPID )memberikan PID yang berbeda
David King

1
@heemayl echo $BASHPID && { echo $BASHPID; }memberikan PID yang sama seperti yang Anda sarankan akan terjadi. Terima kasih atas pendidikannya
David King

@ Davidvid, terima kasih untuk menggunakan perintah uji $BASHPID, itu hal lain yang saya lewatkan.
Wildcard
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.