Apakah bash mendukung kembali referensi dalam ekspansi parameter?

15

Saya memiliki variabel bernama descryang dapat berisi string Blah: -> r1-ae0-2 / [123], -> s7-Gi0-0-1:1-US / Foo, dll saya ingin mendapatkan -> r1-ae0-2, -> s7-Gi0-0-1:1-USbagian dari string. Saat ini saya gunakan descr=$(grep -oP '\->\s*\S+' <<< "$descr"untuk ini. Apakah ada cara yang lebih baik untuk melakukan ini? Apakah mungkin untuk melakukan ini dengan ekspansi parameter?

Martin
sumber

Jawaban:

20

ksh93dan zshmemiliki referensi rujukan belakang (atau lebih tepatnya 1 , referensi untuk menangkap kelompok dalam penggantian) di dalam ${var/pattern/replacement}, tidak bash.

ksh93:

$ var='Blah: -> r1-ae0-2 / [123]'
$ printf '%s\n' "${var/*@(->*([[:space:]])+([^[:space:]]))*/\1}"
-> r1-ae0-2

zsh:

$ var='Blah: -> r1-ae0-2 / [123]'
$ set -o extendedglob
$ printf '%s\n' "${var/(#b)*(->[[:space:]]#[^[:space:]]##)*/$match[1]}"
-> r1-ae0-2

( mkshhalaman manual juga menyebutkan bahwa versi yang akan datang akan mendukungnya ${KSH_MATCH[1]}untuk grup tangkapan pertama. Belum tersedia pada 2017-04-25).

Namun, dengan bash, Anda bisa melakukan:

$ [[ $var =~ -\>[[:space:]]*[^[:space:]]+ ]] &&
  printf '%s\n' "${BASH_REMATCH[0]}"
-> r1-ae0-2

Yang lebih baik karena memeriksa bahwa polanya ditemukan pertama kali.

Jika regexps sistem Anda mendukung \s/ \S, Anda juga dapat melakukan:

re='->\s*\S+'
[[ $var =~ $re ]]

Dengan zsh, Anda bisa mendapatkan kekuatan penuh PCRE dengan:

$ set -o rematchpcre
$ [[ $var =~ '->\s*\S+' ]] && printf '%s\n' $MATCH
-> r1-ae0-2

Dengan zsh -o extendedglob, lihat juga:

$ printf '%s\n' ${(SM)var##-\>[[:space:]]#[^[:space:]]##}
-> r1-ae0-2

Portabel:

$ expr " $var" : '.*\(->[[:space:]]*[^[:space:]]\{1,\}\)'
-> r1-ae0-2

Jika ada beberapa kemunculan pola dalam string, perilaku akan bervariasi dengan semua solusi itu. Namun tidak satu pun dari mereka yang akan memberi Anda daftar yang terpisah dari semua pertandingan seperti di grepsolusi berbasis- GNU Anda .

Untuk melakukan itu, Anda harus melakukan perulangan dengan tangan. Misalnya, dengan bash:

re='(->\s*\S+)(.*)'
while [[ $var =~ $re ]]; do
  printf '%s\n' "${BASH_REMATCH[1]}"
  var=${BASH_REMATCH[2]}
done

Dengan zsh, Anda dapat menggunakan trik semacam ini untuk menyimpan semua pertandingan dalam sebuah array:

set -o extendedglob
matches=() n=0
: ${var//(#m)->[[:space:]]#[^[:space:]]##/${matches[++n]::=$MATCH}}
printf '%s\n' $matches

1 referensi-belakang tidak secara umum menunjuk suatu pola yang merujuk pada apa yang cocok dengan kelompok sebelumnya. Misalnya, \(.\)\1ekspresi reguler dasar cocok dengan satu karakter diikuti oleh karakter yang sama (cocok aa, tidak aktif ab). Itu \1adalah referensi-kembali ke \(.\)grup tangkap dalam pola yang sama.

ksh93memang mendukung back-reference dalam polanya (misalnya ls -d -- @(?)\1akan mencantumkan nama file yang terdiri dari dua karakter yang identik), bukan shell lain. BRE dan PCRE standar mendukung referensi-belakang tetapi bukan ERE standar, meskipun beberapa implementasi ERE mendukungnya sebagai ekstensi. bashIni [[ foo =~ re ]]menggunakan Eres.

[[ aa =~ (.)\1 ]]

tidak akan cocok, tetapi

re='(.)\1'; [[ aa =~ $re ]]

mungkin jika ERE sistem mendukungnya.

Stéphane Chazelas
sumber
9

Anda ingin menghapus semuanya hingga yang pertama ␣->␣(tidak termasuk "panah") dan setelah yang terakhir ␣/(termasuk spasi dan garis miring).

string="Blah: -> r1-ae0-2 / [123]"
string=${string/*->/->}
string=${string/ \/*}

$stringsekarang akan menjadi -> r1-ae0-2.

Dua pergantian yang sama akan berubah -> s7-Gi0-0-1:1-US / Foomenjadi -> s7-Gi0-0-1:1-US.

Kusalananda
sumber
3

Menjawab ini secara pasti adalah mustahil tanpa mengetahui format yang tepat setiap pesan. Namun, sebagai pendekatan umum Anda dapat mencetak bidang spesifik tertentu menggunakan cut:

$ cut -d ' ' -f 2 <<< '-> s7-Gi0-0-1:1-US / Foo'
s7-Gi0-0-1:1-US

Atau Anda dapat mencetak setiap kolom ke-n menggunakanawk :

$ awk -F' ' '{ for (i=2;i<=NF;i+=4) print $i }' <<< '-> r1-ae0-2 / [123], -> s7-Gi0-0-1:1-US / Foo'
r1-ae0-2
s7-Gi0-0-1:1-US
l0b0
sumber