Supprimer un préfixe / suffixe fixe d'une chaîne dans Bash


485

Dans mon bash script, j'ai une chaîne et son préfixe / suffixe. J'ai besoin de supprimer le préfixe / suffixe de la chaîne d'origine.

Par exemple, supposons que j'ai les valeurs suivantes:

string="hello-world"
prefix="hell"
suffix="ld"

Comment arriver au résultat suivant?

result="o-wor"

5
Jetez un coup d'œil au guide avancé de script Bash
tarrsalah

14
Soyez très prudent lorsque vous vous connectez au soi-disant guide de script avancé Bash il contient un mélange de bons conseils et terrible.
tripleee

Réponses:


720
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor

40
Il existe également ## et %%, qui enlèvent autant que possible si $ prefix ou $ suffix contiennent des caractères génériques.
pts

28
Existe-t-il un moyen de combiner les deux en une seule ligne? J'ai essayé ${${string#prefix}%suffix}mais ça ne marche pas.
static_rtti

28
@static_rtti Non, malheureusement, vous ne pouvez pas imbriquer une substitution de paramètres comme celle-ci. Je sais, c'est dommage.
Adrian Frühwirth

87
@ AdrianFrühwirth: toute la langue est une honte, mais c'est tellement utile :)
static_rtti

8
Nvm, "substitution bash" dans Google a trouvé ce que je voulais.
Tyler

89

Utilisation de sed:

$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

Dans la commande sed, le ^caractère correspond au texte commençant par $prefixet le dernier $correspond au texte se terminant par$suffix .

Adrian Frühwirth fait quelques bons points dans les commentaires ci-dessous, mais sedà cet effet peut être très utile. Le fait que le contenu de $ prefix et $ suffix soit interprété par sed peut être bon OU mauvais - tant que vous y prêtez attention, ça devrait aller. La beauté est que vous pouvez faire quelque chose comme ça:

$ prefix='^.*ll'
$ suffix='ld$'
$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

qui peut être ce que vous voulez, et est à la fois plus sophistiqué et plus puissant que la substitution de variables bash. Si vous vous souvenez qu'avec une grande puissance vient une grande responsabilité (comme le dit Spiderman), tout va bien.

Une introduction rapide à sed peut être trouvée à http://evc-cit.info/cit052/sed_tutorial.html

Une note concernant le shell et son utilisation des cordes:

Pour l'exemple particulier donné, les éléments suivants fonctionneraient également:

$ echo $string | sed -e s/^$prefix// -e s/$suffix$//

... mais uniquement parce que:

  1. echo ne se soucie pas du nombre de chaînes dans sa liste d'arguments, et
  2. Il n'y a pas d'espaces dans $ prefix et $ suffix

Il est généralement recommandé de citer une chaîne sur la ligne de commande, car même si elle contient des espaces, elle sera présentée à la commande comme un seul argument. Nous citons $ prefix et $ suffix pour la même raison: chaque commande d'édition à sed sera passée comme une chaîne. Nous utilisons des guillemets doubles car ils permettent une interpolation variable; si nous avions utilisé des guillemets simples, la commande sed aurait obtenu un littéral $prefixet $suffixce n'est certainement pas ce que nous voulions.

Notez également mon utilisation de guillemets simples lors de la définition des variables prefixet suffix. Nous ne voulons certainement pas que quoi que ce soit dans les chaînes soit interprété, nous les citons donc pour qu'aucune interpolation n'ait lieu. Encore une fois, cela peut ne pas être nécessaire dans cet exemple, mais c'est une très bonne habitude à prendre.


8
Malheureusement, c'est un mauvais conseil pour plusieurs raisons: 1) Sans guillemets, $stringest sujet au fractionnement et à la globalisation des mots. 2) $prefixet $suffixpeut contenir des expressions qui sedinterpréteront, par exemple des expressions régulières ou le caractère utilisé comme délimiteur qui cassera toute la commande. 3) Il sedn'est pas nécessaire d' appeler deux fois (vous pouvez à la -e 's///' -e '///'place) et le tuyau peut également être évité. Par exemple, considérez string='./ *'et / ou prefix='./'et voyez-le se briser horriblement à cause de 1)et 2).
Adrian Frühwirth

Note amusante: sed peut prendre presque n'importe quoi comme délimiteur. Dans mon cas, comme j'analysais les répertoires de préfixes hors des chemins, je ne pouvais pas les utiliser /, alors j'ai utilisé à la sed "s#^$prefix##place. (Fragilité: les noms de fichiers ne peuvent pas contenir #. Puisque je contrôle les fichiers, nous sommes en sécurité, là-bas.)
Olie

@Olie Les noms de fichiers peuvent contenir n'importe quel caractère, à l'exception de la barre oblique et du caractère nul, donc à moins que vous ne contrôliez, vous ne pouvez pas supposer qu'un nom de fichier ne contiendra pas certains caractères.
Adrian Frühwirth

Ouais, je ne sais pas ce que je pensais là-bas. iOS peut-être? Je ne sais pas. Les noms de fichiers peuvent certainement contenir "#". Je ne sais pas pourquoi j'ai dit ça. :)
Olie

@Olie: Si j'ai bien compris votre commentaire d'origine, vous disiez que la limitation de votre choix à utiliser #comme délimiteur de sed signifiait que vous ne pouviez pas gérer les fichiers contenant ce caractère.
P Daddy

17

Connaissez-vous la longueur de votre préfixe et suffixe? Dans ton cas:

result=$(echo $string | cut -c5- | rev | cut -c3- | rev)

Ou plus général:

result=$(echo $string | cut -c$((${#prefix}+1))- | rev | cut -c$((${#suffix}+1))- | rev)

Mais la solution d'Adrian Frühwirth est bien cool! Je n'en savais rien!


14

J'utilise grep pour supprimer les préfixes des chemins (qui ne sont pas bien gérés par sed):

echo "$input" | grep -oP "^$prefix\K.*"

\K supprime de la correspondance tous les caractères précédents.


grep -Pest une extension non standard. Plus de puissance pour vous si elle est prise en charge sur votre plate-forme, mais c'est un conseil douteux si votre code doit être raisonnablement portable.
tripleee

@tripleee En effet. Mais je pense qu'un système avec GNU Bash installé a également un grep qui prend en charge PCRE.
Vladimir Petrakovich

1
Non, MacOS par exemple a Bash prêt à l'emploi mais pas GNU grep. Les versions antérieures avaient en fait l' -Poption de BSD grepmais elles l'ont supprimée.
tripleee

9
$ string="hello-world"
$ prefix="hell"
$ suffix="ld"

$ #remove "hell" from "hello-world" if "hell" is found at the beginning.
$ prefix_removed_string=${string/#$prefix}

$ #remove "ld" from "o-world" if "ld" is found at the end.
$ suffix_removed_String=${prefix_removed_string/%$suffix}
$ echo $suffix_removed_String
o-wor

Remarques:

# $ prefix: l'ajout de # garantit que la sous-chaîne "hell" n'est supprimée que si elle est trouvée au début. % $ suffixe: l'ajout de% garantit que la sous-chaîne "ld" n'est supprimée que si elle est trouvée à la fin.

Sans cela, les sous-chaînes "hell" et "ld" seront supprimées partout, même si elles se trouvent au milieu.


Merci pour les notes! qq: dans votre exemple de code, vous avez également une barre oblique /juste après la chaîne, à quoi cela sert-il?
DiegoSalazar

1
/ sépare la chaîne actuelle et la sous-chaîne. sous-chaîne est ici le suffixe de la question publiée.
Vijay Vat


6

Solution petite et universelle:

expr "$string" : "$prefix\(.*\)$suffix"

1
Si vous utilisez Bash, vous ne devriez probablement pas l'utiliser exprdu tout. C'était une sorte d' utilité pratique pour l'évier de cuisine à l'époque de la coque Bourne d'origine, mais elle a désormais dépassé sa date de péremption.
tripleee

5

Utilisation de la réponse @Adrian Frühwirth:

function strip {
    local STRING=${1#$"$2"}
    echo ${STRING%$"$2"}
}

l'utiliser comme ça

HELLO=":hello:"
HELLO=$(strip "$HELLO" ":")
echo $HELLO # hello

0

Je voudrais utiliser des groupes de capture dans regex:

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ set +H # Disables history substitution, can be omitted in scripts.
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/" <<< $string
o-wor
$ string1=$string$string
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/g" <<< $string1
o-woro-wor

((?:(?!(${suffix})).)*)s'assure que le contenu de ${suffix}sera exclu du groupe de capture. En termes d'exemple, c'est la chaîne équivalente à [^A-Z]*. Sinon, vous obtiendrez:

$ perl -pe "s/${prefix}(.*)${suffix}/\1/g" <<< $string1
o-worldhello-wor
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.