Pencocokan pola pada nama jalur di bash


10

Saya ingin bertindak pada daftar subdirektori dalam direktori. Mempertimbangkan:

for x in x86-headers/*/C/populate.sh; do echo $x; done

Ini memberi

x86-headers/elf/C/populate.sh
x86-headers/gl/C/populate.sh
x86-headers/gmp/C/populate.sh
x86-headers/gnome2/C/populate.sh
x86-headers/gtk2/C/populate.sh
x86-headers/jni/C/populate.sh
x86-headers/libc/C/populate.sh

Tapi aku ingin nilai-nilai yang bersesuaian dengan bagian kedua dari jalan, elf, gl, dll saya tahu bagaimana menanggalkan terkemuka x86-headers.

for x in x86-headers/*/C/populate.sh; do i=${x##x86-headers/}; echo $i; done

pemberian yang mana

elf/C/populate.sh
gl/C/populate.sh
gmp/C/populate.sh
gnome2/C/populate.sh
gtk2/C/populate.sh
jni/C/populate.sh
libc/C/populate.sh

Saya juga tahu cara menghapus istilah nanti di jalan. Yaitu turun satu tingkat:

cd x86-headers
for x in */C/populate.sh; do i=${x%%/*}; echo $i; done

memberi

elf
gl
gmp
gnome2
gtk2
jni
libc

Tetapi, mencoba menggabungkan ini tidak berhasil. Yaitu

for x in x86-headers/*/C/populate.sh; do i=${${x##x86-headers}%%/*}; echo $i; done

memberi

bash: ${${x##x86-headers}%%/*}: bad substitution

Tidak diragukan lagi ini adalah sintaks yang salah, tetapi saya tidak tahu sintaks yang benar. Tentu saja mungkin ada cara yang lebih baik untuk melakukan ini. Jika saya menggunakan Python, saya akan menggunakan split /untuk memecah setiap jalur menjadi daftar, dan kemudian memilih elemen kedua, tapi saya tidak tahu bagaimana melakukannya di bash.

EDIT: Terima kasih atas jawabannya. Saya seharusnya juga bertanya, apakah mungkin melakukan ini dengan mudah, dan jika demikian, bagaimana?

Jawaban:


9

Anda tidak dapat menggabungkannya dengan bash(atau POSIXly), Anda harus melakukannya dalam dua langkah.

i=${x#*/}; i=${i%%/*}

Itu portabel untuk setiap shell POSIX. Jika Anda membutuhkan portabilitas ke shell Bourne (tapi mengapa Anda akan menandai pertanyaan Anda / bash kemudian?) Juga jika Anda porting ke Solaris dan dipaksa untuk menggunakan /bin/shalih-alih standar di shsana, atau porting ke sistem lama 20 tahun) , Anda bisa menggunakan pendekatan ini (yang akan berfungsi dengan baik pada shell POSIX):

IFS=/; set -f
set x $x
i=$3

(di atas adalah salah satu kasus yang sangat langka di mana masuk akal untuk membiarkan variabel tidak dikutip).

Hanya sebagai catatan, dengan zsh:

print -rl -- x86-headers/*/C/populate.sh(:h:h:t)

(yang t ail dari h EAD dari h EAD file).

Atau untuk pythonpendekatan Anda :

x=elf/C/populate.sh
i=${${(s:/:)x}[2]}

Tanda titik koma i=${x#*/}; i=${i%%/*}adalah opsional, bukan?
Dimitre Radoulov

3
@ DimitreRadoulov Ini opsional tetapi beberapa shell seperti beberapa versi lama ash/dash akan menjalankan tugas dari kanan ke kiri (yang adalah bagaimana shell Bourne berperilaku) dan menyebabkan masalah dalam kasus ini.
Stéphane Chazelas

7

Ekspansi parameter tidak memungkinkan Anda untuk membuat ekspresi sarang, jadi Anda terpaksa melakukannya dalam dua langkah, dengan tugas:

i=${x##x86-headers/}
i=${i%%/*}

Anda memang memiliki opsi untuk menggunakan array di sini, tapi saya pikir itu akan lebih rumit daripada PE di atas:

IFS=/
set -f # Disable globbing in case unquoted $x has globs in it
i=( $x )
set +f
echo "${i[1]}"

Lalu ada opsi ketiga menggunakan perintah eksternal. Dengan daftar file yang pendek ini biasanya merupakan opsi yang paling tidak efisien, tetapi ini akan menskalakan yang terbaik seiring dengan meningkatnya jumlah file:

# For clarity, assume no names have linebreaks in them
find . -name populate.sh | cut -d / -f 3

Itu tidak berlaku untuk semua kerang, zshmemungkinkan bersarang.
Stéphane Chazelas

3
@StephaneChazelas cukup adil, tetapi pertanyaan ini ditandai bash.
kojiro

2
regex="x86-headers/([^/]*)/.*"
for f in x86-headers/*/C/populate.sh; do [[ $f =~ $regex ]] && echo "${BASH_REMATCH[1]}"; done
elf
gl
gmp
gnome2
gtk2
jni
libc

2

Berhati-hatilah menggunakan konstruk seperti di bawah ini:

# What happen if no files match ?
for x in x86-headers/*/C/populate.sh; do
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done

Karena Anda akan dapat akhirnya melakukan echo *. Dalam hal ini, itu hanya lucu, tapi bisa jauh terburuk jika Anda root, Anda CWDyaitu /dan Anda ingin menghapus beberapa sub direktori dari /tmpbukannya bergema mereka!

Untuk memperbaikinya dalam bash, Anda dapat melakukan:

shopt -s nullglob
for x in x86-headers/*/C/populate.sh; do
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done
shopt -u nullglob

Perhatikan shopt -u nullglobpada akhir untuk mengembalikan perilaku bash default setelah loop.

Solusi yang lebih portabel adalah:

for x in x86-headers/*/C/populate.sh; do
  [ -e $x ] || break
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done

( Substitusi ##dan %%Parameter valid dalam ksh88 ).

Ikuti tautan ini untuk diskusi yang lebih lengkap tentang nullglob .

Perhatikan bahwa 3 solusi di atas gagal jika Anda memiliki direktori perantara dengan spasi di namanya. Untuk mengatasinya, gunakan tanda kutip ganda dalam substitusi variabel:

for x in x86-headers/*/C/populate.sh; do
  [ -e "$x" ] || break
  x="${x##x86-headers/}"
  x="${x%%/*}"
  echo "$x"
done

0

Untuk melakukan pencocokan pola pada nama path dengan mudah, Anda dapat mengatur IFSvariabel shell /dan kemudian menggunakan ekspansi parameter shell.

Melakukan semua ini dalam sebuah subkulit membantu menjaga lingkungan shell induk tidak berubah!

# cf. "The real Bourne shell problem",
# http://utcc.utoronto.ca/~cks/space/blog/programming/BourneShellLists

paths='
x86-headers/elf/C/populate.sh
x86-headers/gl/C/populate.sh
x86-headers/gmp/C/populate.sh
x86-headers/gnome2/C/populate.sh
x86-headers/gtk2/C/populate.sh
x86-headers/jni/C/populate.sh
x86-headers/libc/C/populate.sh
'

IFS='
'

# version 1
for x in $paths; do 
   ( IFS='/'; set -- ${x}; echo "$2" ); 
done


# version 2
set -- ${paths}
for x in $@; do 
   ( IFS='/'; set -- ${x}; echo "$2" ); 
done
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.