Supprimer le dernier caractère d'une chaîne à l'aide de la manipulation de chaîne dans un script shell

188

Je voudrais supprimer le dernier caractère d'une chaîne, j'ai essayé ce petit script:

#! /bin/sh 

t="lkj"
t=${t:-2}
echo $t

mais il affiche "lkj", qu'est-ce que je fais mal?

utilisateur3581976
la source

Réponses:

115

Dans un shell POSIX, la syntaxe ${t:-2}signifie quelque chose de différent - elle se développe à la valeur de tif test définie et non nulle, et sinon à la valeur 2. Pour ajuster un seul caractère par développement de paramètre, la syntaxe que vous souhaitez probablement est la suivante:${t%?}

Notez que ksh93, bashou zsh, ${t:(-2)}ou ${t: -2}(notez l'espace) sont légal comme l' expansion de sous - chaîne , mais probablement pas ce que vous voulez, car ils retournent la sous - chaîne commençant à une position 2 caractères à partir de la fin (il supprime le premier caractère idu chaîne ijk).

Reportez-vous à la section Extension des paramètres de shell du Manuel de référence de Bash pour plus d'informations:

Steeldriver
la source
4
Souhaitez-vous expliquer quelle est la magie derrière "%?" ?
afraisse
8
@afraisse ${parameter%word}supprime le suffixe le plus court correspondant word- voir la section Expansion des paramètres deman bash
steeldriver le
3
Cela fonctionne bien pour Bash 4.1.2: $ {t%?} Pour les gens bloqués avec CentOS / RHEL 6.x
Joey T
186

Avec bash4.2 et plus, vous pouvez faire:

${var::-1}

Exemple:

$ a=123
$ echo "${a::-1}"
12

Notez que pour les anciens bash(par exemple, bash 3.2.5sous OS X), vous devez laisser des espaces entre et après les deux points:

${var: : -1}
cuonglm
la source
13
Cela fonctionne pour la bashversion 4.2-alpha et supérieure, dommage que la version à laquelle j'ai accès soit antérieure. : - /
hjk
2
@iamaziz: De bash changelog, la longueur négative dans a ${var:offset:lenght}été ajoutée uniquement dans bash 4.2. Peut-être que OSX ajoute son propre patch pour bash.
jeudi
1
@cuonglm ni travail: /
iamaziz
1
Ne fonctionne pas sur mac.
shinzou
1
MACsters, regardez la réponse de Russ
P
67

pour supprimer les derniers ncaractères d'une ligne qui ne fait pas usage de sedOU awk:

> echo lkj | rev | cut -c (n+1)- | rev

Ainsi, par exemple, vous pouvez supprimer le dernier caractère en one characterutilisant ceci:

> echo lkj | rev | cut -c 2- | rev

> lk

depuis la revpage de manuel:

DESCRIPTION
L'utilitaire rev copie les fichiers spécifiés sur la sortie standard en inversant l'ordre des caractères dans chaque ligne. Si aucun fichier n'est spécifié, l'entrée standard est lue.

MISE À JOUR:

si vous ne connaissez pas la longueur de la chaîne, essayez:

$ x="lkj"
$ echo "${x%?}"
lk
Networker
la source
62

En utilisant sed, il devrait être aussi rapide que

sed 's/.$//'

Votre seul écho est alors echo ljk | sed 's/.$//'.
En utilisant cela, la chaîne d'une ligne pourrait avoir n'importe quelle taille.

1111161171159459134
la source
10
Notez que dans le cas général, il ne supprime pas le dernier caractère de la chaîne , mais le dernier caractère de chaque ligne de la chaîne .
Stéphane Chazelas
44

Quelques options en fonction du shell:

  • POSIX: t=${t%?}
  • Bourne: t=`expr " $t" : ' \(.*\).'`
  • zsh / yash: t=${t[1,-2]}
  • bash / zsh: t=${t:0:-1}
  • ksh93 / bash / zsh / mksh: t=${t:0:${#t}-1}
  • ksh93 / bash / zsh / mksh: t=${t/%?}
  • ksh93: t=${t/~(E).$/}
  • es: @ {t=$1} ~~ $t *?

Notez que bien que tous soient supposés effacer le dernier caractère , vous constaterez que certaines implémentations (celles qui ne prennent pas en charge les caractères multi-octets) suppriment le dernier octet à la place (ce qui risquerait donc de corrompre le dernier caractère s'il était multi-octets. ).

La exprvariante suppose que $tne se termine pas par plus d'un caractère de nouvelle ligne. Il retournera également un statut de sortie non nul si la chaîne résultante finit par être 0( 000ou même -0avec certaines implémentations). Cela pourrait également donner des résultats inattendus si la chaîne contient des caractères non valides.

Stéphane Chazelas
la source
Nice et complet! Mais ... je suppose que tous ces coques supportent POSIX, donc tout le monde devrait utiliser celui-ci pour être le plus portable. Le plus petit nombre de personnages, aussi!
Russ
@Russ, t=${t%?}n'est pas Bourne mais vous ne rencontrerez probablement pas un shell Bourne de nos jours. ${t%?}fonctionne dans tous les autres cependant.
Stéphane Chazelas
Aucune option de coquille de poisson donnée! Probablement plus populaire que ksh93 ces jours-ci ...
rien333
@ rien333. J'attendrais que l'interface se stabilise un peu. fishest en cours. La version 2.3.0 qui a introduit la fonction stringintégrée n’a pas été publiée au moment de la période de questions. Avec la version que je teste, il vous faut string replace -r '(?s).\z' '' -- $t(et je m'attendrais à ce qu'ils veuillent changer ça, ils devraient changer les drapeaux qu'ils passent à PCRE) ou plus compliqués. Il traite également mal les caractères de nouvelle ligne, et je sais qu'ils envisagent de changer cela également.
Stéphane Chazelas
Upvote pour la réponse POSIX. a confirmé travailler sur Bash 3.2.57 (1)
Avindra Goolcharan
26

La réponse la plus portable et la plus courte est presque certainement:

${t%?}

Cela fonctionne en bash, sh, ash, dash, busybox / ash, zsh, ksh, etc.

Cela fonctionne en utilisant l'expansion des paramètres de shell old-school. Spécifiquement, le %spécifie de supprimer le plus petit suffixe correspondant du paramètre tqui correspond au motif global ?(c'est-à-dire: n'importe quel caractère).

Voir "Supprimer le plus petit modèle de suffixe" ici pour une explication (beaucoup) plus détaillée et plus de fond. Consultez également la documentation de votre shell (par exemple:) man bashsous "Développement des paramètres".


En guise de remarque, si vous souhaitez supprimer le premier caractère à la place, vous utiliserez ${t#?}, puisque les #correspondances se trouvent à l'avant de la chaîne (préfixe) au lieu de l'arrière (suffixe).

Il faut également noter que les deux %et #ont %%et ##versions, qui correspondent à la plus longue version du motif donné au lieu de la plus courte. Les deux ${t%%?}et ${t##?}feraient la même chose que leur opérateur unique dans ce cas, cependant (alors n’ajoutez pas le caractère supplémentaire inutile). En effet, le ?modèle donné ne correspond qu’à un seul caractère. Ajoutez des *éléments non génériques et les choses deviennent plus intéressantes avec %%et ##.

Comprendre les développements de paramètres, ou au moins connaître leur existence et savoir les rechercher, est extrêmement utile pour écrire et déchiffrer des scripts shell de nombreuses versions. Les extensions de paramètres ressemblent souvent à des arcanes dans un shell voodoo parce que ... bien ... ce sont des arcanes dans un vaudou (bien que très bien documentées si vous savez rechercher "une expansion des paramètres"). C'est vraiment bien d'avoir dans la ceinture à outils quand on est coincé dans une coquille, cependant.

Russ
la source
Court et doux, et fonctionne à la fois sur MacOS et Linux!
dbernard
18
t=lkj
echo ${t:0:${#t}-1}

Vous obtenez une sous-chaîne de 0 à la longueur de chaîne -1. Notez cependant que cette soustraction est spécifique à bash et ne fonctionnera pas sur d'autres shells.

Par exemple, dashn'est pas capable d'analyser même

echo ${t:0:$(expr ${#t} - 1)}

Par exemple, sur Ubuntu, /bin/shestdash

Ange
la source
15

Vous pouvez également utiliser headpour imprimer tout sauf le dernier caractère.

$ s='i am a string'
$ news=$(echo -n $s | head -c -1)
$ echo $news
i am a strin

Mais malheureusement, certaines versions de headn'incluent pas l' -option principale . C'est le cas de headcelui fourni avec OS X.

greenbeansugar
la source
5

Il est assez facile de faire en utilisant une expression régulière:

n=2
echo "lkj" | sed "s/\(.*\).\{$n\}/\1/"
unxnut
la source
5

Quelques raffinements. Pour supprimer plusieurs caractères, vous pouvez ajouter plusieurs points d'interrogation. Par exemple, pour supprimer les deux derniers caractères de la variable:, $SRC_IP_MSGvous pouvez utiliser:

SRC_IP_MSG=${SRC_IP_MSG%??}
Yuliskov
la source
4

Juste pour compléter quelques utilisations possibles de pure bash:

#!/bin/bash

# Testing substring removal
STR="Exemple string with trailing whitespace "
echo "'$STR'"
echo "Removed trailing whitespace: '${STR:0:${#STR}-1}'"
echo "Removed trailing whitespace: '${STR/%\ /}'"

La première syntaxe prend une sous-chaîne d'une chaîne, la syntaxe est la suivante: pour la seconde, remarquez le signe, ce qui signifie 'depuis la fin de la ligne' et la syntaxe est
${STRING:OFFSET:LENGTH}
%
${STRING/PATTERN/SUBSTITUTION}

Et voici deux formes plus courtes de ce qui précède

echo "Removed trailing whitespace: '${STR::-1}'"
echo "Removed trailing whitespace: '${STR%\ }'"

Ici encore, remarquez le %signe, signifiant 'Supprimer (c'est-à-dire, remplacer par' ') le motif correspondant le plus court (ici représenté par un espace d'échappement' \ ' à la fin du paramètre PARAMETER - ici nommé STR

CermakM
la source
1

Comme nous pouvons également utiliser php en ligne de commande, ou des scripts shell. C'est parfois utile pour l'analyse syntaxique chirurgicale.

php -r "echo substr('Hello', 0, -1);" 
// Output hell

Avec tuyauterie:

echo "hello" | php -r "echo substr(trim(fgets(STDIN)), 0, -1);"
// Output hell
NVRM
la source
-1

En ksh:

echo ${ORACLE_SID/%?/}
Alex
la source