Di Bash, bagaimana saya bisa memeriksa apakah sebuah string dimulai dengan beberapa nilai?

745

Saya ingin memeriksa apakah sebuah string dimulai dengan "simpul" mis. "Simpul001". Sesuatu seperti

if [ $HOST == user* ]
  then
  echo yes
fi

Bagaimana saya bisa melakukannya dengan benar?


Saya selanjutnya perlu menggabungkan ekspresi untuk memeriksa apakah HOST adalah "user1" atau dimulai dengan "node"

if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

> > > -bash: [: too many arguments

Bagaimana saya bisa melakukannya dengan benar?

Tim
sumber
7
Jangan terlalu tergoda untuk menggabungkan ekspresi. Mungkin terlihat lebih buruk untuk memiliki dua persyaratan terpisah, meskipun Anda dapat memberikan pesan kesalahan yang lebih baik dan membuat skrip Anda lebih mudah untuk di-debug. Saya juga akan menghindari fitur bash. Saklar adalah cara untuk pergi.
hendry

Jawaban:

1073

Cuplikan pada Panduan Skrip Bash Lanjutan ini mengatakan:

# The == comparison operator behaves differently within a double-brackets
# test than within single brackets.

[[ $a == z* ]]   # True if $a starts with a "z" (wildcard matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).

Jadi Anda sudah hampir benar; Anda membutuhkan tanda kurung ganda , bukan tanda kurung tunggal.


Sehubungan dengan pertanyaan kedua Anda, Anda dapat menuliskannya dengan cara ini:

HOST=user1
if  [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes1
fi

HOST=node001
if [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes2
fi

Yang akan bergema

yes1
yes2

ifSintaks Bash sulit digunakan (IMO).

Mark Rushakoff
sumber
12
Maksud Anda regex [[$ a = ~ ^ z. *]]?
JStrahl
3
Jadi, apakah ada perbedaan fungsional antara [[ $a == z* ]]dan [[ $a == "z*" ]]? Dengan kata lain: apakah mereka bekerja secara berbeda? Dan apa yang Anda maksud ketika Anda mengatakan "$ a sama dengan z *"?
Niels Bom
5
Anda tidak memerlukan pemisah pernyataan ";" jika Anda menempatkan "lalu" di jalurnya sendiri
Yaza
6
Hanya untuk kelengkapan: Untuk memeriksa apakah sebuah string SELESAI dengan ...:[[ $a == *com ]]
lepe
4
ABS adalah pilihan referensi yang tidak menguntungkan - sangat banyak W3Schools of bash, penuh dengan konten yang sudah ketinggalan zaman dan contoh praktik buruk; saluran freenode #bash telah mencoba untuk mencegah penggunaannya setidaknya sejak 2008 . Adakah peluang untuk memperbaiki di BashFAQ # 31 ? (Saya juga sudah menyarankan wiki Bash-Hacker, tapi sudah turun sedikit sekarang).
Charles Duffy
207

Jika Anda menggunakan versi terbaru dari Bash (v3 +), saya sarankan operator perbandingan regex Bash =~, misalnya,

if [[ "$HOST" =~ ^user.* ]]; then
    echo "yes"
fi

Untuk mencocokkan this or thatdalam suatu regex, gunakan |, misalnya,

if [[ "$HOST" =~ ^user.*|^host1 ]]; then
    echo "yes"
fi

Catatan - ini adalah sintaks ekspresi reguler 'tepat'.

  • user*berarti usedan nol atau lebih kejadian r, jadi usedan userrrrakan cocok.
  • user.*berarti userdan nol atau lebih kemunculan karakter apa pun, jadi user1, userXakan cocok.
  • ^user.*berarti cocok dengan pola user.*di awal $ HOST.

Jika Anda tidak terbiasa dengan sintaks ekspresi reguler, coba rujuk ke sumber ini .

brabster
sumber
Terima kasih, Brabster! Saya menambahkan ke posting asli pertanyaan baru tentang cara menggabungkan ekspresi di jika cluase.
Tim
2
Sayang sekali bahwa jawaban yang diterima tidak mengatakan apa-apa tentang sintaks ekspresi reguler.
CarlosRos
20
FYI, =~operator Bash hanya melakukan pencocokan ekspresi reguler ketika sisi kanan UNQUOTED. Jika Anda mengutip sisi kanan, "Bagian mana pun dari pola dapat dikutip untuk memaksa agar dicocokkan dengan string." (1.) pastikan untuk selalu menempatkan ekspresi reguler di sebelah kanan yang tidak dikutip dan (2.) jika Anda menyimpan ekspresi reguler Anda dalam sebuah variabel, pastikan untuk TIDAK mengutip sisi kanan ketika Anda melakukan ekspansi parameter.
Trevor Boyd Smith
145

Saya selalu mencoba untuk tetap dengan POSIX shdaripada menggunakan ekstensi Bash, karena salah satu poin utama dari scripting adalah portabilitas (selain menghubungkan program, bukan menggantinya).

Di sh, ada cara mudah untuk memeriksa kondisi "is-prefix".

case $HOST in node*)
    # Your code here
esac

Mengingat seberapa tua, misterius, dan kekasaran (dan Bash bukan obatnya: Ini lebih rumit, kurang konsisten, dan kurang portabel), saya ingin menunjukkan aspek fungsional yang sangat bagus: Sementara beberapa elemen sintaksis seperti casesudah ada di dalamnya , konstruksi yang dihasilkan tidak berbeda dari pekerjaan lain. Mereka dapat disusun dengan cara yang sama:

if case $HOST in node*) true;; *) false;; esac; then
    # Your code here
fi

Atau bahkan lebih pendek

if case $HOST in node*) ;; *) false;; esac; then
    # Your code here
fi

Atau bahkan lebih pendek (hanya untuk menyajikan !sebagai elemen bahasa - tetapi ini adalah gaya buruk sekarang)

if ! case $HOST in node*) false;; esac; then
    # Your code here
fi

Jika Anda suka secara eksplisit, buat elemen bahasa Anda sendiri:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }

Bukankah ini sebenarnya cukup bagus?

if beginswith node "$HOST"; then
    # Your code here
fi

Dan karena shpada dasarnya hanya pekerjaan dan daftar string (dan proses internal, dari mana pekerjaan terdiri), kita sekarang bahkan dapat melakukan beberapa pemrograman fungsional ringan:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }
checkresult() { if [ $? = 0 ]; then echo TRUE; else echo FALSE; fi; }

all() {
    test=$1; shift
    for i in "$@"; do
        $test "$i" || return
    done
}

all "beginswith x" x xy xyz ; checkresult  # Prints TRUE
all "beginswith x" x xy abc ; checkresult  # Prints FALSE

Ini elegan. Bukannya saya menganjurkan penggunaan shuntuk sesuatu yang serius - itu rusak terlalu cepat pada persyaratan dunia nyata (tidak ada lambda, jadi kita harus menggunakan string. Tapi panggilan fungsi bersarang dengan string tidak mungkin, pipa tidak mungkin, dll.)

Jo So
sumber
12
+1 Tidak hanya portabel ini, ini juga dapat dibaca, idiomatik, dan elegan (untuk skrip shell). Ini juga meluas secara alami ke beberapa pola; case $HOST in user01 | node* ) ...
tripleee
Apakah ada nama untuk pemformatan kode jenis ini? if case $HOST in node*) true;; *) false;; esac; then Saya sudah melihatnya di sana-sini, bagi mata saya itu terlihat agak mengerut.
Niels Bom
@NielsBom Saya tidak tahu persis apa yang Anda maksud dengan memformat, tetapi poin saya adalah bahwa kode shell sangat komposable . Karena caseperintah adalah perintah, mereka bisa masuk ke dalam if ... then.
Jo So
Saya bahkan tidak melihat mengapa itu komposable, saya tidak mengerti cukup skrip shell untuk itu :-) Pertanyaan saya adalah tentang bagaimana kode ini menggunakan tanda kurung yang tidak cocok dan titik koma ganda. Itu tidak terlihat seperti skrip shell yang pernah saya lihat sebelumnya, tapi saya mungkin terbiasa melihat skrip bash lebih dari skrip sh jadi mungkin itu.
Niels Bom
Catatan: Seharusnya beginswith() { case "$2" in "$1"*) true;; *) false;; esac; }sebaliknya jika $1memiliki literal *atau ?mungkin memberikan jawaban yang salah.
LLFourn
80

Anda dapat memilih hanya bagian dari string yang ingin Anda periksa:

if [ "${HOST:0:4}" = user ]

Untuk pertanyaan tindak lanjut Anda, Anda bisa menggunakan OR :

if [[ "$HOST" == user1 || "$HOST" == node* ]]
martin clayton
sumber
8
Anda harus menggandakannya${HOST:0:4}
Jo So
@Jo Jadi: Apa alasannya?
Peter Mortensen
@PeterMortensen, cobaHOST='a b'; if [ ${HOST:0:4} = user ] ; then echo YES ; fi
Jo So
60

Saya lebih suka metode lain yang sudah diposting, tetapi beberapa orang suka menggunakan:

case "$HOST" in 
    user1|node*) 
            echo "yes";;
        *)
            echo "no";;
esac

Edit:

Saya telah menambahkan alternatif Anda ke pernyataan kasus di atas

Dalam versi yang diedit, Anda memiliki terlalu banyak tanda kurung. Seharusnya terlihat seperti ini:

if [[ $HOST == user1 || $HOST == node* ]];
Dijeda sampai pemberitahuan lebih lanjut.
sumber
Terima kasih, Dennis! Saya menambahkan ke posting asli pertanyaan baru tentang cara menggabungkan ekspresi di jika cluase.
Tim
11
"some people like ...": yang ini lebih portabel untuk semua versi dan cangkang.
carlosayam
Dengan pernyataan kasus, Anda dapat mengabaikan kutipan di sekitar variabel karena tidak ada pemisahan kata. Saya tahu ini tidak ada gunanya dan tidak konsisten, tetapi saya ingin meninggalkan tanda kutip di sana untuk membuatnya secara lokal lebih menarik secara visual.
Jo So
Dan dalam kasus saya, saya harus meninggalkan tanda kutip sebelumnya): "/ *") tidak berfungsi, / *) berhasil. (Saya mencari string yang dimulai dengan /, yaitu, jalur absolut)
Josiah Yoder
36

Sementara saya menemukan sebagian besar jawaban di sini cukup benar, banyak dari mereka mengandung Bashisme yang tidak perlu. Ekspansi parameter POSIX memberi Anda semua yang Anda butuhkan:

[ "${host#user}" != "${host}" ]

dan

[ "${host#node}" != "${host}" ]

${var#expr}menghapus pencocokan awalan terkecil exprdari ${var}dan mengembalikannya. Oleh karena itu jika ${host}tidak tidak memulai dengan user( node),${host#user} ( ${host#node}) adalah sama dengan ${host}.

exprmemungkinkan fnmatch()wildcard, dengan demikian ${host#node??}dan teman juga bekerja.

dhke
sumber
2
Saya berpendapat bahwa bashism [[ $host == user* ]]mungkin diperlukan, karena jauh lebih mudah dibaca daripada [ "${host#user}" != "${host}" ]. Dengan demikian Anda dapat mengontrol lingkungan tempat skrip dijalankan (menargetkan versi terbaru bash), yang pertama lebih disukai.
x-yuri
2
@ x-yuri Terus terang, saya hanya mengemas ini menjadi has_prefix()fungsi dan tidak pernah melihatnya lagi.
dhke
30

Sejak # memiliki makna dalam Bash, saya mendapatkan solusi berikut.

Selain itu saya lebih suka mengemas string dengan "" untuk mengatasi spasi, dll.

A="#sdfs"
if [[ "$A" == "#"* ]];then
    echo "Skip comment line"
fi
ozma
sumber
Inilah yang saya butuhkan. Terima kasih!
Ionică Bizău
terima kasih, saya juga bertanya-tanya bagaimana cara mencocokkan string dimulai dengan blah:, sepertinya ini adalah jawabannya!
Anentropic
12

Menambahkan sedikit detail sintaksis ke jawaban peringkat tertinggi Mark Rushakoff.

Ekspresi

$HOST == node*

Bisa juga ditulis sebagai

$HOST == "node"*

Efeknya sama. Pastikan wildcard berada di luar teks yang dikutip. Jika wildcard ada di dalam tanda kutip, itu akan ditafsirkan secara harfiah (yaitu bukan sebagai wildcard).

MrPotatoHead
sumber
8

@OP, untuk kedua pertanyaan Anda, Anda dapat menggunakan case / esac:

string="node001"
case "$string" in
  node*) echo "found";;
  * ) echo "no node";;
esac

Pertanyaan kedua

case "$HOST" in
 node*) echo "ok";;
 user) echo "ok";;
esac

case "$HOST" in
 node*|user) echo "ok";;
esac

Atau Bash 4.0

case "$HOST" in
 user) ;&
 node*) echo "ok";;
esac
ghostdog74
sumber
Catatan yang ;&hanya tersedia di Bash> = 4.
Dijeda sampai pemberitahuan lebih lanjut.
6
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

tidak bekerja, karena semua [, [[dan testmengakui tata bahasa nonrecursive yang sama. Lihat bagian EKSPRESI KONDISI pada halaman Bash man Anda.

Sebagai tambahan, kata SUSv3

Perintah kondisional yang diturunkan oleh KornShell (braket ganda [[]] ) telah dihapus dari deskripsi bahasa perintah shell dalam proposal awal. Keberatan muncul bahwa masalah sebenarnya adalah penyalahgunaan perintah uji ( [ ), dan memasukkannya ke dalam shell adalah cara yang salah untuk memperbaiki masalah. Alih-alih, dokumentasi yang tepat dan kata baru yang dilindungi shell ( ! ) Sudah cukup.

Pengujian yang memerlukan beberapa operasi pengujian dapat dilakukan pada tingkat shell menggunakan doa individu perintah uji dan logika shell, daripada menggunakan flag -rawan kesalahan -o pengujian .

Anda harus menulis seperti ini, tetapi tes tidak mendukungnya:

if [ $HOST == user1 -o $HOST == node* ];
then
echo yes
fi

pengujian menggunakan = untuk kesetaraan string, dan yang lebih penting itu tidak mendukung pencocokan pola.

case/ esacMemiliki dukungan yang baik untuk pencocokan pola:

case $HOST in
user1|node*) echo yes ;;
esac

Ini memiliki manfaat tambahan yang tidak bergantung pada Bash, dan sintaksnya portabel. Dari Spesifikasi Unix Tunggal , Bahasa Perintah Shell :

case word in
    [(]pattern1) compound-list;;
    [[(]pattern[ | pattern] ... ) compound-list;;] ...
    [[(]pattern[ | pattern] ... ) compound-list]
esac
hanya seseorang
sumber
1
[dan testBash builtins serta program eksternal. Coba type -a [.
Dijeda sampai pemberitahuan lebih lanjut.
Banyak terima kasih telah menjelaskan masalah dengan "senyawa atau", @ hanya seseorang - mencari sesuatu seperti itu! Bersulang! Catatan PS (tidak berhubungan dengan OP): if [ -z $aa -or -z $bb ]; ... memberikan " bash: [: -atau: diharapkan operator biner "; Namun if [ -z "$aa" -o -z "$bb" ] ; ...berlalu.
sdaau
2

grep

Lupa kinerja, ini POSIX dan terlihat lebih bagus daripada casesolusi:

mystr="abcd"
if printf '%s' "$mystr" | grep -Eq '^ab'; then
  echo matches
fi

Penjelasan:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
2

Saya mengubah jawaban @ markrushakoff untuk menjadikannya fungsi yang bisa dipanggil:

function yesNo {
  # Prompts user with $1, returns true if response starts with y or Y or is empty string
  read -e -p "
$1 [Y/n] " YN

  [[ "$YN" == y* || "$YN" == Y* || "$YN" == "" ]]
}

Gunakan seperti ini:

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] Y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] yes
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n]
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] ddddd
false

Ini adalah versi yang lebih kompleks yang menyediakan nilai default yang ditentukan:

function toLowerCase {
  echo "$1" | tr '[:upper:]' '[:lower:]'
}

function yesNo {
  # $1: user prompt
  # $2: default value (assumed to be Y if not specified)
  # Prompts user with $1, using default value of $2, returns true if response starts with y or Y or is empty string

  local DEFAULT=yes
  if [ "$2" ]; then local DEFAULT="$( toLowerCase "$2" )"; fi
  if [[ "$DEFAULT" == y* ]]; then
    local PROMPT="[Y/n]"
  else
    local PROMPT="[y/N]"
  fi
  read -e -p "
$1 $PROMPT " YN

  YN="$( toLowerCase "$YN" )"
  { [ "$YN" == "" ] && [[ "$PROMPT" = *Y* ]]; } || [[ "$YN" = y* ]]
}

Gunakan seperti ini:

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N]
false

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N] y
true

$ if yesNo "asfd" y; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false
Mike Slinn
sumber
-5

Hal lain yang dapat Anda lakukan adalah catmengetahui apa yang Anda gema dan lakukaninline cut -c 1-1

Richard Tang
sumber