Cómo encontrar el último campo usando 'cortar'

310

Sin usar sedo awk, solo cut , ¿cómo obtengo el último campo cuando el número de campos es desconocido o cambia con cada línea?

noobcoder
fuente
8
¿Estás enamorado del cutcomando :)? ¿Por qué no otros comandos de Linux?
Jayesh Bhoi
77
Sin sedo awk: perl -pe 's/^.+\s+([^\s]+)$/$1/'.
jordanm
3
posible duplicado de cómo dividir una cadena en shell y obtener el último campo
Amir Ali Akbari
44
@MestreLion Muchas veces las personas leen una pregunta para encontrar una solución a una variación de un problema. Este comienza con la premisa falsa que cutrespalda algo que no. Pero pensé que era útil, ya que obliga al lector a considerar el código que es más fácil de seguir. Yo quería una rápida, manera simple de utilización cutsin necesidad de utilizar múltiples sintaxis para awk, grep, sed, etc. La revcosa hizo el truco; muy elegante, y algo que nunca he considerado (incluso si es torpe para otras situaciones). También me gustó leer los otros enfoques de las otras respuestas.
Beejor
3
Vine aquí un problema de la vida real: quiero encontrar todas las diferentes extensiones de archivo en un árbol de origen, para actualizar un archivo .gitattributes con. Así find | cut -d. -f<last>es la inclinación natural
studog

Respuestas:

680

Podrías probar algo como esto:

echo 'maps.google.com' | rev | cut -d'.' -f 1 | rev

Explicación

  • rev revierte "maps.google.com" para ser moc.elgoog.spam
  • cut usa punto (es decir, '.') como delimitador, y elige el primer campo, que es moc
  • por último, lo revertimos nuevamente para obtener com
zedfoxus
fuente
66
No está usando solo cutpero está sin sedo awk. Entonces, ¿qué opina OP?
Jayesh Bhoi
77
@tom OP ha hecho más preguntas que solo esto en las últimas horas. Según nuestras interacciones con el OP sabemos que awk / sed / etc. no están permitidos en su tarea, pero no se ha hecho referencia a rev. Así que valió la pena
intentarlo
44
@zfus ya veo. Puede querer pegar otro revdespués.
Tom
17
doble revideal ideal!
Ford Guo
66
Impresionante, simple, perfecto, gracias por la explicación también: no hay suficientes personas que expliquen cada paso en largas cadenas de comandos canalizados
Pete
128

Utiliza un parámetro de expansión. Esto es mucho más eficiente que cualquier tipo de comando externo, cut(o grep) incluido.

data=foo,bar,baz,qux
last=${data##*,}

Ver BashFAQ # 100 para una introducción a la manipulación de cadenas nativas en bash.

Charles Duffy
fuente
3
@ErwinWessels: Porque bash es realmente lento. Use bash para ejecutar tuberías, no para procesar datos en masa. Quiero decir, esto es genial si ya tienes una línea de texto en una variable de shell, o si quieres while IFS= read -ra array_var; do :;done <(cmd)procesar algunas líneas. Pero para un archivo grande, ¡rev | cut | rev es probablemente más rápido! (Y, por supuesto awk será más rápido que eso.)
Peter Cordes
2
@PeterCordes, awk será más rápido para un archivo grande, seguro, pero se necesita un poco de información para superar los costos de inicio de factor constante. (También existen shells, como ksh93, con un rendimiento más cercano a awk, donde la sintaxis dada en esta respuesta sigue siendo válida; bash es excepcionalmente lento, pero ni siquiera está cerca de la única opción disponible).
Charles Duffy
1
Gracias @PeterCordes; como de costumbre, supongo que cada herramienta tiene sus casos de uso.
Erwin Wessels
1
Esta es, con mucho, la forma más rápida y concisa de recortar una sola variable dentro de un bashscript (suponiendo que ya esté usando un bashscript). No es necesario llamar a nada externo.
Ken Sharp
1
@Balmipour, ... sin embargo, rev es específico de cualquier sistema operativo que esté utilizando que lo proporcione, no está estandarizado en todos los sistemas UNIX. Consulte la lista de capítulos de la sección POSIX sobre comandos y utilidades ; no está allí. Y de hecho no${var##prefix_pattern} es específico de bash; está en el estándar POSIX sh , consulte el final de la sección 2.6.2 (vinculado), por lo que , a diferencia , siempre está disponible en cualquier shell compatible. rev
Charles Duffy
89

No es posible usar solo cut. Aquí hay una manera de usar grep:

grep -o '[^,]*$'

Reemplace la coma por otros delimitadores.

tom
fuente
3
Para hacer lo contrario, y encontrar todo excepto el último campo:grep -o '^.*,'
Ariel
2
Esto fue especialmente útil, porque revagregué un problema de caracteres unicode multibyte en mi caso.
Brice
3
Intenté hacer esto en MinGW pero mi versión grep no admite -o, así que usé el sed 's/^.*,//'que reemplaza todos los caracteres hasta e incluyendo la última coma con una cadena vacía.
TamaMcGlinn
46

¿Sin awk? ... Pero es tan simple con awk:

echo 'maps.google.com' | awk -F. '{print $NF}'

AWK es una herramienta mucho más poderosa para tener en tu bolsillo. -F si para el separador de campo NF es el número de campos (también representa el índice del último)

Amir Mehler
fuente
2
Esto es universal y funciona exactamente como se esperaba cada vez. En este escenario, usar cutpara lograr la salida final del OP es como usar una cuchara para "cortar" el bistec (juego de palabras :)). awkEs el cuchillo de carne.
Hickory420
3
Evite el uso innecesario de echoeso puede ralentizar el script para archivos largos awk -F. '{print $NF}' <<< 'maps.google.com'.
Anil_M
14

Hay múltiples formas Puedes usar esto también.

echo "Your string here"| tr ' ' '\n' | tail -n1
> here

Obviamente, la entrada de espacio en blanco para el comando tr debe reemplazarse con el delimitador que necesita.

rjni
fuente
¡Gracias! algo que funciona en busybox sh 1.0.0 :)
kevinf
1
Esto se siente como la respuesta más simple para mí, menos pipas y un significado más claro
joeButler
1
Eso no funcionará para un archivo completo, que es lo que probablemente significó el OP.
Amir
7

Esta es la única solución posible para usar nada más que cortar:

echo "cadena" | cortar -d '.' -f2- [ repetir_siguiendo_parte_por_ver_para_todo_de_memoria :] | cortar -d '.' -f2-

Con esta solución, el número de campos puede ser desconocido y variar de vez en cuando. Sin embargo, como la longitud de la línea no debe exceder los caracteres o campos LINE_MAX, incluido el carácter de nueva línea, un número arbitrario de campos nunca puede ser parte como una condición real de esta solución.

Sí, una solución muy tonta, pero creo que la única que cumple los criterios.

Un amigo
fuente
2
Agradable. Solo toma el último '.' fuera de "cadena" y esto funciona.
Matt
2
Me encanta cuando todos dicen que algo es imposible y luego alguien interviene con una respuesta que funciona. Incluso si de hecho es muy tonto.
Beejor
Uno podría iterar cut -f2-en un bucle hasta que la salida ya no cambie.
loa_in_
4

Si su cadena de entrada no contiene barras diagonales, entonces puede usar basenamey una subshell:

$ basename "$(echo 'maps.google.com' | tr '.' '/')"

Esto no utiliza sedo awksino que además no utiliza cuttampoco, así que no estoy muy seguro de si se califica como una respuesta a la pregunta de su redacción.

Esto no funciona bien si se procesan cadenas de entrada que pueden contener barras diagonales. Una solución para esa situación sería reemplazar la barra diagonal con algún otro carácter que sepa que no es parte de una cadena de entrada válida. Por ejemplo, el carácter pipe ( |) tampoco está permitido en los nombres de archivo, por lo que esto funcionaría:

$ basename "$(echo 'maps.google.com/some/url/things' | tr '/' '|' | tr '.' '/')" | tr '|' '/'
jstine
fuente
2

lo siguiente implementa la sugerencia de un amigo

#!/bin/bash
rcut(){

  nu="$( echo $1 | cut -d"$DELIM" -f 2-  )"
  if [ "$nu" != "$1" ]
  then
    rcut "$nu"
  else
    echo "$nu"
  fi
}

$ export DELIM=.
$ rcut a.b.c.d
d
usuario2166700
fuente
2
Necesita comillas dobles alrededor de los argumentos echopara que esto funcione de manera confiable y sólida. Consulte stackoverflow.com/questions/10067266/…
tripleee el
0

Si tiene un archivo llamado filelist.txt que es una lista de rutas como la siguiente: c: /dir1/dir2/file1.h c: /dir1/dir2/dir3/file2.h

entonces puede hacer esto: rev filelist.txt | cortar -d "/" -f1 | Rdo

aperson1961
fuente
0

Agregar un enfoque a esta vieja pregunta solo por el gusto de hacerlo:

$ cat input.file # file containing input that needs to be processed
a;b;c;d;e
1;2;3;4;5
no delimiter here
124;adsf;15454
foo;bar;is;null;info

$ cat tmp.sh # showing off the script to do the job
#!/bin/bash
delim=';'
while read -r line; do  
    while [[ "$line" =~ "$delim" ]]; do
        line=$(cut -d"$delim" -f 2- <<<"$line")
    done
    echo "$line"
done < input.file

$ ./tmp.sh # output of above script/processed input file
e
5
no delimiter here
15454
info

Además de bash, solo se usa corte. Bueno, y echo, supongo.

Kaffe Myers
fuente
Meh, ¿por qué no solo eliminas el corte por completo y solo usas bash ... x] para while read -r line; do echo ${line/*;}; done <input.fileobtener el mismo resultado.
Kaffe Myers
-1

Me di cuenta de que si solo nos aseguramos de que exista un delimitador final, funciona. Entonces, en mi caso, tengo delimitadores de comas y espacios en blanco. Añado un espacio al final;

$ ans="a, b"
$ ans+=" "; echo ${ans} | tr ',' ' ' | tr -s ' ' | cut -d' ' -f2
b
AnneTheAgile
fuente
Y ans="a, b, c"produce b, que no cumple con los requisitos de "número de campos son desconocidos o cambian con cada línea" .
jww