Un comando para imprimir solo los últimos 3 caracteres de una cadena

30

Sé que el cutcomando puede imprimir los primeros ncaracteres de una cadena, pero ¿cómo seleccionar los últimos ncaracteres?

Si tengo una cadena con un número variable de caracteres, ¿cómo puedo imprimir solo los últimos tres caracteres de la cadena? p.ej.

la salida "ilimitada" necesaria es "ted"
La salida "987654" necesaria es "654"
La salida "123456789" necesaria es "789"
Odisea
fuente

Respuestas:

52

¿Por qué nadie ha dado la respuesta obvia?

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

... o lo un poco menos obvio

grep -o '...$'

Es cierto que el segundo tiene el inconveniente de que las líneas con menos de tres caracteres desaparecen; pero la pregunta no definió explícitamente el comportamiento para este caso.

G-Man dice 'restablecer a Mónica'
fuente
66
ogrep -o '.\{3\}$'
Avinash Raj
3
oecho "unlimited" | python -c "print raw_input()[-3:]"
Kiro
8
@Kiro o "echo unlimited" | java -jar EnterpriseWordTrimmer.jar, pero no creo que sea realmente necesario introducir un lenguaje más pesado para la manipulación de personajes.
wchargin
11
@WChargin que olvidastejava -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
66
grep -o -P '.{0,3}$'imprimirá los últimos 3 caracteres incluso si la línea tiene menos de 3 caracteres. -Pevita tener que escapar de los aparatos ortopédicos.
Raghu Dodda
43

Manteniéndolo simple - cola

No deberíamos necesitar una expresión regular, o más de un proceso, solo para contar caracteres.
El comando tail, que a menudo se usa para mostrar las últimas líneas de un archivo, tiene una opción -c( --bytes), que parece ser la herramienta adecuada para esto:

$ printf 123456789 | tail -c 3
789

(Cuando está en un shell, tiene sentido usar un método como en la respuesta de mikeserv, porque ahorra iniciar el proceso para tail).

Personajes reales de Unicode?

Ahora, pides los últimos tres caracteres ; Eso no es lo que te da esta respuesta: ¡genera los últimos tres bytes !

Siempre que cada carácter sea un byte, tail -csimplemente funciona. Por lo tanto, se puede utilizar si el conjunto de caracteres es ASCII, ISO 8859-1o una variante.

Si tiene una entrada Unicode, como en el UTF-8formato común , el resultado es incorrecto:

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

En este ejemplo, usando UTF-8, los caracteres griegos alfa, beta y gamma tienen dos bytes de longitud:

$ printf 123αβγ | wc -c  
9

La opción -mpuede contar al menos los caracteres Unicode reales:

printf 123αβγ | wc -m
6

Ok, los últimos 6 bytes nos darán los últimos 3 caracteres:

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

Por lo tanto, tailno admite el manejo de caracteres generales, y ni siquiera lo intenta (ver más abajo): maneja líneas de tamaño variable, pero no caracteres de tamaño variable.

Digámoslo de esta manera: tailes correcto para la estructura del problema a resolver, pero incorrecto para el tipo de datos.

GNU coreutils

Mirando más, resulta que los que te coreutils GNU, el conjunto de herramientas básicas como sed, ls, taily cut, no está internacionalizado todavía completamente. Que se trata principalmente de soportar Unicode.
Por ejemplo, cutsería un buen candidato para usar en lugar de la cola aquí para el soporte del personaje; Tiene opciones para trabajar en bytes o caracteres, -c( --bytes) y -m( --chars);

¡Solo eso -m/ --charses, a partir de la versión
cut (GNU coreutils) 8.21, 2013,
no implementado!

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.


Consulte también esta respuesta a ¿No puede usar `cut -c` (` --characters`) con UTF-8? .

Volker Siegel
fuente
2
En realidad, la mayoría de las otras respuestas parecen manejar bien Unicode, siempre que la configuración regional actual especifique la codificación UTF-8. Solo la suya y la cutsolución basada en Glenn Jackman no parecen hacerlo.
Ilmari Karonen
@IlmariKaronen Cierto, gracias por la pista. He editado, con algunos detalles adicionales.
Volker Siegel
1
Tenga en cuenta que POSIX especifica explícitamente que taildebe tratar con bytes y no con caracteres. Una vez hice un parche para agregar una nueva opción para seleccionar también personajes, pero creo que nunca se fusionaron: - /
Martin Tournoij
No funciona en modo de archivo, comotail -c3 -n10 /var/log/syslog
Suncatcher
@Suncatcher Lo intenté, y funcionó. ¿Cuál es el problema que ves? Su comando tail -c3 -n10 /var/log/syslogle pide las últimas 10 líneas, y eso funciona para mí. Utiliza la opción -c3, y luego la opción conflictiva -n10. La última opción tiene prioridad.
Volker Siegel
36

Si el texto está en una variable de entorno llamada STRING, usted puede hacer esto en una bash, zsho mkshshell:

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

O

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

que también tiene el beneficio de trabajar con ksh93 de donde proviene esa sintaxis.

El punto es que el :tiene que ser separado del -, de lo contrario se convierte en el ${var:-default}operador del shell Bourne.

La sintaxis equivalente en los shells zsho yashes:

printf '%s\n' "${STRING[-3,-1]}"
DopeGhoti
fuente
2
¿Cómo se llama ese tipo de sintaxis / operación para poder buscar más información?
Tulains Córdova
66
Se llama expansión de subcadena . Es una especie de expansión de parámetros . La forma general es $ {parámetro: desplazamiento: longitud} , pero el campo de longitud es opcional (y, como puede ver, se ha omitido en la respuesta anterior). DopeGhoti también podría haber escrito ${STRING:(-3):3}(especificando el campo de longitud ), ${STRING: -3}(con un espacio entre el :y el -), o ${STRING: -3:3}.
G-Man dice 'Reincorporar a Mónica'
En este caso, especificar la longitud de 3es algo discutible, ya que es pedir "los tres caracteres del tercero desde el último carácter, inclusive", que resulta ser una operación idéntica en términos prácticos a "Todos los caracteres en adelante desde el tercero desde el último , inclusive ".
DopeGhoti
13

Utilizando awk:

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

Si la cadena está en una variable, puede hacer:

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

Eso despoja a los últimos tres caracteres del valor de me $vargusta:

${var%???}

... y luego se despoja de la cabeza de $vartodo, excepto de lo que se acaba de quitar:

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

Este método tiene sus ventajas y desventajas. En el lado positivo, es totalmente portátil POSIX y debería funcionar en cualquier carcasa moderna. Además, si $varno contiene al menos tres caracteres nada pero el de salida \nse imprime ewline. Por otra parte, si desea que se imprima en ese caso, necesita un paso adicional como:

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

De esa manera $last3solo está vacío si $varcontiene 3 o menos bytes. Y $varsolo se sustituye $last3si $last3está vacío o unset, y sabemos que no es unsetporque lo configuramos.

mikeserv
fuente
Eso es bastante ordenado +1. Aparte: ¿alguna razón por la que no cita sus printfcadenas de formato?
jasonwryan
¿Por qué no solo usar ${VARNAME:(-3)}(presumiendo bash)?
DopeGhoti
1
Gracias por aclararlo; tiene sentido, incluso si parece (para mí) un poco extraño ...
jasonwryan
1
@DopeGhoti, simplemente porque es una suposición que casi nunca hago. Esto funciona tan bien bashcomo en cualquier otro shell que reclame la compatibilidad POSIX.
mikeserv
3
@odyssey: el problema nocsh es uno de los proyectiles modernos compatibles con POSIX que menciono aquí, desafortunadamente. La especificación POSIX-shell se modela después , que se modela a sí misma después de una combinación de ambos y los shells tradicionales de estilo Bourne. incorporó la excelente funcionalidad de control de trabajo y la antigua redirección de E / S de los estilos Bourne. También agregó algunas cosas, como los conceptos de manipulación de cadenas que demuestro arriba. Es probable que esto no funcione en ningún tradicional , que yo sepa, lamento decirlo. kshcshkshcshcsh
mikeserv
7

Puedes hacer esto, pero esto es un poco ... excesivo:

for s in unlimited 987654 123456789; do
    rev <<< $s | cut -c 1-3 | rev
done 
ted
654
789
Glenn Jackman
fuente
3

La solución a prueba de balas para cadenas 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")

O usar:

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

para evitar el manejo de datos con formato incorrecto.

Ejemplo:

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

Produce algo como esto:

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

No depende de la configuración regional (es decir, funciona con LC_ALL=C). Bash, sed, grep, awk, revRequerirá algo como esto:LC_ALL=en_US.UTF-8

Solución común:

  • Recibir bytes
  • Detectar codificación
  • Decodificar bytes a caracteres
  • Extraer caracteres
  • Codificar caracteres en bytes

Puede detectar la codificación con uchardet . Ver también proyectos relacionados .

Puede decodificar / codificar con Encode en Perl, códecs en Python 2.7

Ejemplo :

Extraiga los últimos tres caracteres de la cadena utf-16le y convierta estos caracteres a 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"
)

Ver también: perlunitut , Python 2 Unicode HOWTO

Evgeny Vereshchagin
fuente
echoCuál es tu fuente a prueba de balas?
mikeserv
@mikeserv, decode/encodees mi fuente a prueba de balas. Limpié mi respuesta.
Evgeny Vereshchagin
Esto también depende de la configuración regional para garantizar que funcione correctamente, ya que un conjunto de bytes puede reflejar diferentes caracteres en diferentes conjuntos de caracteres. "Funciona" LC_ALL=Cporque es una configuración muy "tonta", pero puede romperse cuando intenta pasar una cadena UTF-8 a SHIFT-5, o una cadena SHIFT-5 a KOI8, etc.
Martin Tournoij
@Carpetsmoker, gracias. ¿Podrías explicar tu comentario? Supongo que eso perl -CAO -e 'print substr($ARGV[0], -3)'funciona bien. ASe espera que los elementos @ARGV sean cadenas codificadas en UTF-8, OSTDOUT estará en UTF-8.
Evgeny Vereshchagin
parece que le cuentas sobre la asignación autf8_str
Evgeny Vereshchagin
1

¿Qué pasa con el uso de "expr" o "rev"?

Una respuesta similar a la proporcionada por @ G-Man : expr "$yourstring" : '.*\(...\)$' tiene el mismo inconveniente que la solución grep.

Un truco bien conocido es combinar "cortar" con "rev": echo "$yourstring" | rev | cut -n 1-3 | rev

Gildux
fuente
La revsolución se parece mucho a Glenn Jackman
Jeff Schaller
Tienes razón @Jeff_Schaller: me perdí el de
Glenn
0

Obtenga el tamaño de la cadena con:

size=${#STRING}

Luego obtenga la subcadena del último n carácter:

echo ${STRING:size-n:size}

Por ejemplo:

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

daría:

789
Esref
fuente
0

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

Si desea imprimir los primeros trece caracteres del principio

Ankit Vishwakarma
fuente
-1

printf no funcionará si la cadena tiene espacios en ella.

Debajo del código para la cadena con espacio

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

nux

Saurabh
fuente
Um, si printfno funciona, entonces estás haciendo algo muy mal.
Kusalananda
1
@Kusalananda: según el comando que muestra Saurabh, lo intentaron printf $str(en lugar de printf "$str"o printf '%s' "$str"). Y sí, printf $strestá muy mal. ( echo -n $strno es mucho mejor.)
G-Man dice 'Reinstate Monica'