Bash prend-il en charge les références arrières dans l'expansion des paramètres?

15

J'ai une variable nommée descrqui peut contenir une chaîne Blah: -> r1-ae0-2 / [123], -> s7-Gi0-0-1:1-US / Fooetc. Je veux obtenir la -> r1-ae0-2, -> s7-Gi0-0-1:1-USpartie de la chaîne. En ce moment j'utilise descr=$(grep -oP '\->\s*\S+' <<< "$descr"pour ça. Y a-t-il une meilleure manière de faire cela? Est-il également possible de le faire avec l'expansion des paramètres?

Martin
la source

Réponses:

20

ksh93et zshont un support de référence arrière (ou plus précisément 1 , des références aux groupes de capture dans le remplacement) à l'intérieur ${var/pattern/replacement}, non 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

(la mkshpage de manuel mentionne également que les futures versions le prendront en charge ${KSH_MATCH[1]}pour le premier groupe de capture. Pas encore disponible à partir du 25/04/2017).

Cependant, avec bash, vous pouvez faire:

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

Ce qui est mieux car il vérifie que le motif est trouvé en premier.

Si les regexps de votre système prennent en charge \s/ \S, vous pouvez également faire:

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

Avec zsh, vous pouvez obtenir toute la puissance des PCRE avec:

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

Avec zsh -o extendedglob, voir aussi:

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

Portablement:

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

S'il existe plusieurs occurrences du modèle dans la chaîne, le comportement variera avec toutes ces solutions. Cependant, aucun d'eux ne vous donnera une liste séparée par des sauts de ligne de toutes les correspondances comme dans votre grepsolution basée sur GNU .

Pour ce faire, vous devez effectuer le bouclage à la main. Par exemple, avec bash:

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

Avec zsh, vous pouvez recourir à ce genre d'astuce pour stocker toutes les correspondances dans un tableau:

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

1 Les références arrières désignent plus communément un modèle qui fait référence à ce qui correspondait à un groupe précédent. Par exemple, l' \(.\)\1expression régulière de base correspond à un seul caractère suivi de ce même caractère (il correspond à aa, pas à ab). C'est \1une référence arrière à ce \(.\)groupe de capture dans le même modèle.

ksh93prend en charge les références arrières dans ses modèles (par exemple ls -d -- @(?)\1listera les noms de fichiers qui se composent de deux caractères identiques), pas d'autres shells. Les BRE et PCRE standard prennent en charge les références arrières mais pas les ERE standard, bien que certaines implémentations ERE le prennent en charge en tant qu'extension. bash« s [[ foo =~ re ]]utilisations ERE.

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

ne correspondra pas, mais

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

peut si les ERE du système le prennent en charge.

Stéphane Chazelas
la source
9

Vous souhaitez tout supprimer jusqu'au premier ␣->␣(sans compter la "flèche") et après le dernier ␣/(y compris l'espace et la barre oblique).

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

$stringsera maintenant -> r1-ae0-2.

Les deux mêmes substitutions se transformeraient -> s7-Gi0-0-1:1-US / Fooen -> s7-Gi0-0-1:1-US.

Kusalananda
la source
3

Il est impossible de répondre définitivement à cette question sans connaître le format exact de chaque message. Cependant, comme approche générale, vous pouvez imprimer certains champs spécifiques en utilisant cut:

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

Ou vous pouvez imprimer chaque nième colonne en utilisantawk :

$ 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
la source