Une commande pour imprimer uniquement les 3 derniers caractères d'une chaîne

30

Je sais que la cutcommande peut imprimer les premiers ncaractères d'une chaîne mais comment sélectionner les derniers ncaractères?

Si j'ai une chaîne avec un nombre variable de caractères, comment puis-je imprimer uniquement les trois derniers caractères de la chaîne. par exemple.

la sortie "illimitée" nécessaire est "ted"
La sortie "987654" requise est "654"
La sortie "123456789" requise est "789"
odyssée
la source

Réponses:

52

Pourquoi personne n'a-t-il donné la réponse évidente?

sed 's/.*\(...\)/\1/'

… Ou un peu moins évident

grep -o '...$'

Certes, le second a l'inconvénient que les lignes de moins de trois caractères disparaissent; mais la question n'a pas défini explicitement le comportement pour ce cas.

G-Man dit 'Réintégrez Monica'
la source
6
ougrep -o '.\{3\}$'
Avinash Raj
3
ouecho "unlimited" | python -c "print raw_input()[-3:]"
Kiro
8
@Kiro ou "echo unlimited" | java -jar EnterpriseWordTrimmer.jar, mais je ne pense pas qu'il soit vraiment nécessaire d'introduire un langage plus lourd pour la manipulation des personnages.
wchargin
11
@WChargin que vous avez oubliéjava -server -Xms300M -Xmx3G -XX:+UseParallelGC -cp /path/to/all/the/jars/ -Dinput.interactive=false -Dinput.pipe=true -Dconfig.file=/path/to/config/last-three-letters.cfg -jar ...
hjk
6
grep -o -P '.{0,3}$'imprime les 3 derniers caractères même si la ligne comporte moins de 3 caractères. -Pévite d'avoir à échapper aux accolades.
Raghu Dodda
43

Rester simple - queue

Nous ne devons pas avoir besoin d'une expression régulière, ni de plusieurs processus, juste pour compter les caractères.
La commande tail, souvent utilisée pour afficher les dernières lignes d'un fichier, a une option -c( --bytes), qui semble être juste le bon outil pour cela:

$ printf 123456789 | tail -c 3
789

(Lorsque vous êtes dans un shell, il est logique d'utiliser une méthode comme dans la réponse de mikeserv, car cela évite de démarrer le processus pour tail.)

De vrais personnages Unicode?

Maintenant, vous demandez les trois derniers caractères ; Ce n'est pas ce que cette réponse vous donne: elle sort les trois derniers octets !

Tant que chaque caractère est un octet, tail -cça fonctionne. Il peut donc être utilisé si le jeu de caractères est ASCII, ISO 8859-1ou une variante.

Si vous avez une entrée Unicode, comme dans le UTF-8format commun , le résultat est incorrect:

$ printf 123αβγ | tail -c 3
�γ

Dans cet exemple, en utilisant UTF-8, les caractères grecs alpha, bêta et gamma ont une longueur de deux octets:

$ printf 123αβγ | wc -c  
9

L'option -m peut au moins compter les vrais caractères unicode:

printf 123αβγ | wc -m
6

Ok, donc les 6 derniers octets nous donneront les 3 derniers caractères:

$ printf 123αβγ | tail -c 6
αβγ

Donc, tailne prend pas en charge la gestion des caractères généraux, et il n'essaie même pas (voir ci-dessous): il gère les lignes de taille variable, mais pas les caractères de taille variable.

Disons-le de cette façon: tail est juste pour la structure du problème à résoudre, mais pas pour le type de données.

GNU coreutils

Au -delà, il se trouve que Thee coreutils GNU, la collection d'outils de base comme sed, ls, tailet cut, ne sont pas encore totalement internationalisé. Il s'agit principalement de prendre en charge Unicode.
Par exemple, cutserait un bon candidat à utiliser au lieu de queue ici pour le support des personnages; Il a des options pour travailler sur les octets ou les caractères, -c( --bytes) et -m(--chars );

Seul ce -m/ --charsn'est, à partir de la version
cut (GNU coreutils) 8.21, 2013,
pas implémenté!

De info cut:

`-c CHARACTER-LIST'
`--characters=CHARACTER-LIST'
     Select for printing only the characters in positions listed in CHARACTER-LIST.  
     The same as `-b' for now, but internationalization will change that.


Voir aussi cette réponse à Vous ne pouvez pas utiliser `cut -c` (` --characters`) avec UTF-8? .

Volker Siegel
la source
2
En fait, la plupart des autres réponses semblent bien gérer Unicode, tant que les paramètres régionaux actuels spécifient le codage UTF-8. Seules la cutsolution basée sur la vôtre et sur Glenn Jackman ne semble pas le faire.
Ilmari Karonen
@IlmariKaronen True, merci pour l'astuce. J'ai édité, avec quelques détails supplémentaires.
Volker Siegel
1
Notez que POSIX spécifie explicitement qui taildoit traiter les octets et non les caractères. J'ai fait une fois un patch pour ajouter une nouvelle option pour sélectionner également des personnages, mais je pense que cela n'a jamais été fusionné: - /
Martin Tournoij
Ne fonctionne pas en mode fichier, commetail -c3 -n10 /var/log/syslog
Suncatcher
@Suncatcher j'ai essayé, et cela a fonctionné. Quel est le problème que vous voyez? Votre commande tail -c3 -n10 /var/log/syslogdemande les 10 dernières lignes, et cela fonctionne pour moi. Vous utilisez l'option -c3, puis l'option en conflit -n10. La dernière option est prioritaire.
Volker Siegel
36

Si votre texte est dans une variable shell appelée STRING, vous pouvez le faire dans un bash, zshou mkshshell:

printf '%s\n' "${STRING:(-3)}"

Ou

printf '%s\n' "${STRING: -3}"

qui a également l'avantage de travailler avec ksh93 d'où vient cette syntaxe.

Le fait est que le :doit être séparé du -, sinon il devient l' ${var:-default}opérateur du shell Bourne.

La syntaxe équivalente dans les shells zshou yashest:

printf '%s\n' "${STRING[-3,-1]}"
DopeGhoti
la source
2
Comment s'appelle ce type de syntaxe / opération pour que je puisse rechercher plus d'informations?
Tulains Córdova
6
Cela s'appelle Expansion de sous- chaîne . C'est une sorte d' extension de paramètres . La forme générale est $ {paramètre: offset: length} , mais le champ de longueur est facultatif (et, comme vous pouvez le voir, il a été omis dans la réponse ci-dessus). DopeGhoti aurait également pu écrire ${STRING:(-3):3}(en spécifiant le champ de longueur ), ${STRING: -3}(avec un espace entre le :et le -), ou ${STRING: -3:3}.
G-Man dit `` Réinstalle Monica '' le
Dans ce cas, spécifier la longueur de 3est quelque peu théorique car cela demande "les trois caractères du troisième au dernier caractère, inclus", ce qui s'avère être une opération identique en termes pratiques à "Tous les caractères à partir du troisième à partir du dernier" , inclusivement ".
DopeGhoti
13

Utilisant awk:

awk '{ print substr( $0, length($0) - 2, length($0) ) }' file
ted
654
789
jasonwryan
la source
11

Si la chaîne est dans une variable, vous pouvez faire:

printf %s\\n "${var#"${var%???}"}"

Cela supprime les trois derniers caractères de la valeur de $varlike:

${var%???}

... puis se dépouille de la tête de $vartout, mais ce qui vient d'être dépouillé comme:

${var#"${var%???}"}

Cette méthode a ses avantages et ses inconvénients. Du côté positif, il est entièrement portable POSIX et devrait fonctionner dans n'importe quelle coque moderne. De plus, si $varne contient pas au moins trois caractères, rien d'autre que la ligne \newline de fin n'est imprimé. Là encore, si vous souhaitez l'imprimer dans ce cas, vous avez besoin d'une étape supplémentaire comme:

last3=${var#"${var%???}"}
printf %s\\n "${last3:-$var}"

De cette façon, il $last3n'est jamais vide que s'il $varcontient 3 octets ou moins. Et $varn'est jamais substitué à $last3si $last3est vide ou unset- et nous savons que ce n'est pas unsetparce que nous venons de le définir.

Mikeserv
la source
C'est plutôt bien rangé +1. À part: pour quelle raison vous ne citez pas vos printfchaînes de format?
jasonwryan
Pourquoi ne pas simplement utiliser ${VARNAME:(-3)}(en supposant bash)?
DopeGhoti
1
Merci de clarifier; est logique, même si cela me semble un peu étrange ...
jasonwryan
1
@DopeGhoti - simplement parce que c'est une supposition que je ne fais presque jamais. Cela fonctionne aussi bien dans bashque dans n'importe quel autre shell revendiquant la comapibilité POSIX.
mikeserv
3
@odyssey - Le problème est cshest pas parmi les modernes, compatibles POSIX coquilles que je mentionne ici, malheureusement. La spécification POSIX-shell est modélisée ksh, qui s'est modelée sur une combinaison des deux cshet des coquilles traditionnelles de style Bourne. kshincorpore à la fois cshl'excellente fonctionnalité de contrôle des tâches et la redirection d'E / S des anciens styles Bourne. Il a également ajouté certaines choses - telles que les concepts de manipulation de chaînes que je démontre ci-dessus. Cela ne fonctionnera probablement pas dans n'importe quel traditionnel cshpour autant que je sache, je suis désolé de le dire.
mikeserv
7

Vous pouvez le faire, mais c'est un peu ... excessif:

for s in unlimited 987654 123456789; do
    rev <<< $s | cut -c 1-3 | rev
done 
ted
654
789
glenn jackman
la source
3

La solution pare-balles pour les cordes utf-8:

utf8_str=$'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82' # привет

last_three_chars=$(perl -CAO -e 'print substr($ARGV[0], -3)' "$utf8_str")

Ou utiliser:

last_three_chars=$(perl -MEncode -CO -e '
  print substr(decode("UTF-8", $ARGV[0], Encode::FB_CROAK), -3)
' "$utf8_str")

pour empêcher le traitement incorrect des données.

Exemple:

perl -MEncode -CO -e '
  print substr(decode("UTF-8", $ARGV[0], Encode::FB_CROAK), -3)
' $'\xd0\xd2\xc9\xd7\xc5\xd4' # koi8-r привет

Produit quelque chose comme ceci:

utf8 "\xD0" does not map to Unicode at /usr/lib/x86_64-linux-gnu/perl/5.20/Encode.pm line 175.

Ne dépend pas des paramètres régionaux (c'est-à-dire fonctionne avec LC_ALL=C). Bash, sed, grep, awk, revExiger quelque chose comme ceci:LC_ALL=en_US.UTF-8

Solution commune:

  • Recevoir des octets
  • Détecter l'encodage
  • Décoder les octets en caractères
  • Extraire les charaсters
  • Encoder le caractère en octets

Vous pouvez détecter l'encodage avec uchardet . Voir également les projets associés .

Vous pouvez décoder / encoder avec Encode en Perl, les codecs en Python 2.7

Exemple :

Extraire les trois derniers caractères de la chaîne utf-16le et convertir ces caractères en utf-8

utf16_le_str=$'\xff\xfe\x3f\x04\x40\x04\x38\x04\x32\x04\x35\x04\x42\x04' # привет

chardet <<<"$utf16_le_str"  # outputs <stdin>: UTF-16LE with confidence 1.0

last_three_utf8_chars=$(perl -MEncode -e '
    my $chars = decode("utf-16le", $ARGV[0]);
    my $last_three_chars = substr($chars, -3);
    my $bytes = encode("utf-8", $last_three_chars);
    print $bytes;
  ' "$utf16_le_str"
)

Voir aussi: perlunitut , Python 2 Unicode HOWTO

Evgeny Vereshchagin
la source
echoest votre source pare-balles?
mikeserv
@mikeserv, decode/encodeest ma source pare-balles. Nettoyé ma réponse.
Evgeny Vereshchagin
Cela dépend également des paramètres régionaux pour garantir qu'il fonctionne correctement, car un ensemble d'octets peut refléter différents caractères dans différents jeux de caractères. Cela "fonctionne" LC_ALL=Ccar c'est un paramètre très "stupide", mais il peut se casser lorsque vous essayez de passer une chaîne UTF-8 à SHIFT-5, ou une chaîne SHIFT-5 à KOI8, etc.
Martin Tournoij
@Carpetsmoker, merci. Pourriez-vous expliquer votre commentaire? Je suppose que cela perl -CAO -e 'print substr($ARGV[0], -3)'fonctionne bien. Ales éléments @ARGV devraient être des chaînes codées en UTF-8, OSTDOUT sera en UTF-8.
Evgeny Vereshchagin
on dirait que vous parlez de l'affectation àutf8_str
Evgeny Vereshchagin
1

Qu'en est-il de l'utilisation de "expr" ou "rev"?

Une réponse similaire à celle fournie par @ G-Man : expr "$yourstring" : '.*\(...\)$' elle présente le même inconvénient que la solution grep.

Une astuce bien connue consiste à combiner "couper" avec "rev": echo "$yourstring" | rev | cut -n 1-3 | rev

gildux
la source
La revsolution ressemble beaucoup à celle de Glenn Jackman
Jeff Schaller
Vous avez raison @Jeff_Schaller: J'ai raté celui de glenn :-(
gildux
0

Obtenez la taille de la chaîne avec:

size=${#STRING}

Ensuite, obtenez la sous-chaîne du dernier n caractère:

echo ${STRING:size-n:size}

Par exemple:

STRING=123456789
n=3
size=${#STRING}
echo ${STRING:size-n:size}

donnerait:

789
Esref
la source
0

tail -n 1 revisions.log | awk '{print substr ($ 0, 0, longueur ($ 0) - (longueur ($ 0) -13))}'

Si vous souhaitez imprimer les treize premiers caractères du début

Ankit Vishwakarma
la source
-1

printf ne fonctionnera pas si la chaîne contient des espaces.

Code ci-dessous pour la chaîne avec espace

str="Welcome to Linux"
echo -n $str | tail -c 3

nux

Saurabh
la source
Si printfça ne marche pas, alors vous faites quelque chose de très mal.
Kusalananda
1
@Kusalananda: Sur la base de la commande que montre Saurabh, ils ont essayé printf $str(plutôt que printf "$str"ou printf '%s' "$str"). Et, oui, printf $strc'est très faux. (ce echo -n $strn'est pas beaucoup mieux.)
G-Man dit 'Reinstate Monica'