Bagaimana cara melewatkan regex ketika menemukan jalur direktori di bash?

14

Saya telah menulis skrip bash kecil untuk menemukan apakah direktori bernama anacondaatau minicondadi pengguna saya $HOME. Tetapi tidak menemukan miniconda2direktori di rumah saya.

Bagaimana saya bisa memperbaikinya?

if [ -d "$HOME"/"(ana|mini)conda[0-9]?" ]; then
    echo "miniconda directory is found in your $HOME"
else
    echo "anaconda/miniconda is not found in your $HOME"
fi

PS: Jika saya punya [ -d "$HOME"/miniconda2 ]; then, maka ia menemukan direktori miniconda2 jadi saya pikir kesalahan terletak pada bagian"(ana|mini)conda[0-9]?"

Saya ingin skripnya menjadi umum. Bagi saya, ini miniconda2 tetapi untuk beberapa pengguna lain mungkin anaconda2, miniconda3 dan sebagainya.

Jenny
sumber
Pengguna lain mungkin menggunakan anaconda_2 atau -2 atau -may2019. Jadi bukankah xxxconda * lebih baik?
WinEunuuchs2Unix
2
Ekspansi nama file Bash menggunakan ekspresi glob, bukan regex.
Peter Cordes

Jawaban:

13

Ini adalah hal yang sangat sulit dilakukan dengan baik.

Pada dasarnya, -dhanya akan menguji satu argumen - bahkan jika Anda bisa mencocokkan nama file menggunakan ekspresi reguler.

Salah satu caranya adalah membalik masalah, dan menguji direktori untuk kecocokan regex alih-alih menguji kecocokan regex untuk direktori. Dengan kata lain, loop semua direktori dalam $HOMEmenggunakan glob shell sederhana, dan uji masing-masing terhadap regex Anda, memecahkan pertandingan, akhirnya menguji apakah BASH_REMATCHarray tidak kosong:

#!/bin/bash

for d in "$HOME"/*/; do
  if [[ $d =~ (ana|mini)conda[0-9]? ]]; then
    break;
  fi
done

if ((${#BASH_REMATCH[@]} > 0)); then
    echo "anaconda/miniconda directory is found in your $HOME"
  else
    echo "anaconda/miniconda is not found in your $HOME"
fi

Cara alternatif adalah dengan menggunakan glob shell diperpanjang di tempat regex, dan menangkap setiap pertandingan glob dalam array. Kemudian uji apakah array tidak kosong:

#!/bin/bash

shopt -s extglob nullglob

dirs=( "$HOME"/@(ana|mini)conda?([0-9])/ )

if (( ${#dirs[@]} > 0 )); then
  echo "anaconda/miniconda directory is found in your $HOME"
else
  echo "anaconda/miniconda is not found in your $HOME"
fi

Trailing /memastikan bahwa hanya direktori yang cocok; yang nullglobmencegah shell dari kembali string yang tak tertandingi dalam kasus nol pertandingan.


Untuk membuat rekursif, atur globstaropsi shell ( shopt -s globstar) dan kemudian masing-masing: -

  • (versi regex): for d in "$HOME"/**/; do

  • (versi glob diperpanjang): dirs=( "$HOME"/**/@(ana|mini)conda?([0-9])/ )

Steeldriver
sumber
1
Saya akan pergi rute array. Anda dapat menggunakan ?([0-9])sebagai @(|[0-9])- ?(...)cocok dengan nol atau satu, sama dengan ?kuantifikasi regex .
glenn jackman
2
Anda bahkan tidak perlu extglob adalah Anda menggunakan brace expansion (ini menghasilkan semua kemungkinan nama yang cocok):~/{ana,mini}conda{0..9}*/
xenoid
Apakah ada pula untuk mengedit salah satu dari solusi ini sehingga akan terus bahkan jika miniatau anacondadipasang di $HOME/sub-directories? Misalnya$HOME/sub-dir1/sub-dir2/miniconda2
Jenny
1
@ Jenny, silakan lihat edit saya mengenaiglobstar
steeldriver
1
@terdon ya saya tidak benar-benar ingin turun ke lubang kelinci tentang apa yang "benar" untuk dicocokkan - Saya hanya mengambil regex OP apa adanya untuk tujuan menggambarkan pendekatan umum
steeldriver
9

Memang, seperti yang telah disebutkan, ini rumit. Pendekatan saya adalah sebagai berikut:

  • gunakan finddan kemampuan regex untuk menemukan direktori yang dimaksud.
  • biarkan findmencetak xuntuk setiap direktori yang ditemukan
  • simpan xes dalam sebuah string
  • jika string tidak kosong, maka salah satu direktori ditemukan.

Jadi:

xString=$(find $HOME -maxdepth 1 \
                     -type d \
                     -regextype egrep \
                     -regex "$HOME/(ana|mini)conda[0-9]?" \
                     -printf 'x');
if [ -n "$xString" ]; then
    echo "found one of the directories";
else
    echo "no match.";
fi

Penjelasan:

  • find $HOME -maxdepth 1menemukan semua yang di bawah ini $HOME tetapi membatasi pencarian ke satu tingkat (yaitu: itu tidak muncul kembali menjadi subdirektori).
  • -type dmembatasi pencarian hanya untuk directories
  • -regextype egrepmemberitahu findjenis ekspresi reguler yang kita hadapi. Ini diperlukan karena hal-hal seperti [0-9]?dan (…|…)agak spesial dan find tidak mengenalinya secara default.
  • -regex "$HOME/(ana|mini)conda[0-9]?"adalah ekspresi reguler aktual yang ingin kita cari
  • -printf 'x'hanya mencetak xuntuk setiap hal yang memenuhi kondisi sebelumnya.
PerlDuck
sumber
Ketika ada kecocokan. -bash: -regex: command not found found one of the directories
Jenny
Hai PerlDuck: Terima kasih. Jawaban yang bagus juga. Tapi saya mendapatkan kesalahan untuk printfMisalnya ketika saya menjalankan skrip, itu berjalan ok tetapi tidak menemukan perintah printf ketika tidak ada yang cocok tapi saya pikir itu karena tidak ada yang mungkin dicetak? -bash: -printf: command not found no match.
Jenny
3
@ Jenny Anda mungkin salah ketik ketika menyalinnya, karena ini berfungsi dengan baik untuk saya. -printfbukan perintah tetapi argumen untuk find. Itulah yang dilakukan garis miring terbalik di akhir baris sebelumnya.
wjandrea
1
Saya sarankan -quitsetelah mencetak path yang ditemukan, kecuali jika Anda ingin terus mendeteksi ambiguitas.
Peter Cordes
Dan mengapa tidak mencetak jalur yang sebenarnya? Anda sudah memilikinya, jadi sepertinya memalukan untuk membuangnya dan menggunakannya xsebagai gantinya:foundDir=$(find $HOME -maxdepth 1 -type d -regextype egrep -regex "$HOME/(ana|mini)conda[0-9]?" -print -quit); echo "found $foundDir"
terdon
2

Anda dapat mengulang daftar nama direktori yang ingin Anda uji dan bertindak jika salah satu dari mereka ada:

a=0
for i in {ana,mini}conda{,2}; do
  if [ -d "$i" ]; then
    unset a
    break
  fi
done
echo "anaconda/miniconda directory is ${a+not }found in your $HOME"

Solusi ini jelas tidak memungkinkan untuk kekuatan regex penuh, tetapi shell globbing dan brace ekspansi sama setidaknya dalam kasus yang Anda tunjukkan. Loop keluar segera setelah satu direktori ada dan menghapus variabel yang ditetapkan sebelumnya a. Pada echobaris berikutnya , ekspansi parameter ${a+not } tidak akan berkembang jika adiatur (= tidak ada dir yang ditemukan) dan “tidak”.

pencuci mulut
sumber
1

Pekerjaan yang mungkin dilakukan adalah mencari miniconda dan anaconda secara terpisah seperti yang ditunjukkan di bawah ini

if [ -d "$HOME"/miniconda* ] || [ -d "$HOME"/anaconda* ]; then
    echo "miniconda directory is found in your $HOME"
else
    echo "anaconda/miniconda is not found in your $HOME"
fi

Tetapi jika seseorang memiliki saran, saya ingin tahu mengapa kita tidak dapat melewati regex saat mencari direktori.

Jenny
sumber
2
Saya memutakhirkan ini - tetapi kemudian menyadari itu akan rusak jika pengguna memiliki lebih dari satu direktori yang cocok (misalnya miniconda DAN miniconda2)
steeldriver
@steeldriver: "itu akan rusak jika pengguna memiliki lebih dari satu direktori yang cocok" Ya, itu memang benar. Apakah Anda punya saran bagaimana cara memperbaikinya?
Jenny
@ Jenny Gunakan array, seperti dalam jawaban steeldriver. shopt -s nullglob; dirs=( "$HOME"/miniconda* "$HOME"/anaconda* ); if (( ${#dirs[@]} > 0 )); then ...
wjandrea
Jika Anda mengganti ] || [dengan -oitu setidaknya tidak boleh pecah jika kedua direktori ditemukan karena kedua direktori gumpalan dicari dalam tes yang sama.
Phoenix
@steeldriver dan Jenny: Anda mungkin ingin memecahkan ambiguitas daripada hanya memilih satu. Buat pengguna menentukan direktori mereka alih-alih memilih yang salah. (mis. edit skrip untuk mengatur nama dir alih-alih menjalankan kode deteksi otomatis.)
Peter Cordes