web-dev-qa-db-fra.com

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

Je sais que la commande cut peut imprimer les premiers n caractères d'une chaîne mais comment sélectionner les derniers n caractè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" 
 "987654" la sortie requise est "654" 
 "123456789" la sortie nécessaire est "789"
31
odyssey

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 ne définissait pas explicitement le comportement dans ce cas.

Rester simple - queue

Nous ne devrions 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 le bon outil pour cela:

$ printf 123456789 | tail -c 3
789

(Lorsque vous êtes dans un Shell, il est judicieux d'utiliser une méthode comme dans la réponse de mikeserv, car elle enregistre le démarrage du 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 Fonctionne juste. Il peut donc être utilisé si le jeu de caractères est ASCII, ISO 8859-1 ou une variante.

Si vous avez une entrée Unicode, comme dans le format commun UTF-8, 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 sont longs 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
αβγ

Ainsi, tail ne prend pas en charge la gestion des caractères généraux, et il n'essaye 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

En regardant plus loin, il s'avère que vous GNU coreutils, la collection d'outils de base comme sed, ls, tail et cut, n'est pas encore complètement internationalisé. Il s'agit principalement de prendre en charge Unicode.
Par exemple, cut serait un bon candidat à utiliser à la place de tail 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 -m/--chars Est, à partir de la version
cut (GNU coreutils) 8.21, 2013,
pas mis en œuvre!

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 ceci réponse à Vous ne pouvez pas utiliser `cut -c` (` --characters`) avec UTF-8? .

45
Volker Siegel

Si votre texte est dans une variable Shell appelée STRING, vous pouvez le faire dans un bash, zsh ou mksh Shell:

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 le ${var:-default} opérateur du Bourne Shell.

La syntaxe équivalente dans les shells zsh ou yash est:

printf '%s\n' "${STRING[-3,-1]}"
37
DopeGhoti

Utilisation de awk:

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

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 $var comme:

${var%???}

... puis se déshabille de la tête de $var tout mais ce qui vient d'être retiré 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 quel Shell moderne. Également si $var ne contient pas au moins trois caractères rien mais le dernier \nla nouvelle ligne est imprimée. Là encore, si vous voulez il imprimé dans ce cas, vous avez besoin d'une étape supplémentaire comme:

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

De cette façon $last3 n'est vide que si $var contient 3 octets ou moins. Et $var ne remplace que $last3 si $last3 est vide ou unset - et nous savons que ce n'est pas unset car nous venons de le définir.

11
mikeserv

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
7
glenn jackman

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 qu'il fonctionne avec LC_ALL=C). Bash, sed, grep, awk, rev nécessitent 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 chardet . Voir aussi projets associés .

Vous pouvez décoder/encoder avec Encode en Perl, codecs in 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

3

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

Une réponse similaire à celle fournie par @ G-Man : expr "$yourstring" : '.*\(...\)$' Elle a 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

1
gildux

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

0
Ankit Vishwakarma

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
0
Esref

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

0
Saurabh