¿Cómo recuperar la primera palabra de la salida de un comando en bash?

126

Tengo un comando, por ejemplo: echo "word1 word2". Quiero poner una tubería ( |) y obtener word1 del comando.

echo "word1 word2" | ....

No sé qué poner después de la tubería.

Neuquino
fuente

Respuestas:

201

Awk es una buena opción si tienes que lidiar con espacios en blanco finales porque se encargará de ti:

echo "   word1  word2 " | awk '{print $1;}' # Prints "word1"

Sin embargo, Cut no se ocupará de esto:

echo "  word1  word2 " | cut -f 1 -d " " # Prints nothing/whitespace

'cortar' aquí no imprime nada / espacio en blanco, porque lo primero antes de un espacio era otro espacio.

mattbh
fuente
¿Es necesario el punto y coma?
Alice Purcell el
1
Debe ser un espacio en blanco "inicial" (al comienzo de la cadena), no "final".
user202729
@AlicePurcell Lo probé sin; y funcionó para mí (MBP 10.14.2)
Samy Bencherif
1
Esto no funciona si la cadena es, por ejemplo, "primera palabra, segunda palabra", ya que ese comando awk delimita por espacio
Roger Oba
@RogerOba Esa no era la pregunta del OP, pero puede usar -F","para anular el separador de campo predeterminado (un espacio) con una coma.
pjd
70

No es necesario utilizar comandos externos. Bash en sí mismo puede hacer el trabajo. Suponiendo "word1 word2" que obtuvo de algún lugar y se almacenó en una variable, por ejemplo

$ string="word1 word2"
$ set -- $string
$ echo $1
word1
$ echo $2
word2

ahora puede asignar $ 1, o $ 2, etc. a otra variable si lo desea.

ghostdog74
fuente
11
+1 para usar solo shell integrado y stdin. @Matt M. --significa stdin, por lo que $stringse pasa como stdin. stdinestá separada por un espacio en blanco en argumentos $1, $2, $3, etc. - al igual que cuando un Bash Evalúa programa argumentos (por ejemplo, comprobación $1, $2etc.), este enfoque se aprovecha de la tendencia de la cáscara para dividir el stdinen argumentos de forma automática, eliminando la necesidad de awko cut.
Caleb Xu
3
@CalebXu No es estándar, setestablece los argumentos del shell.
Guido
9
word1=$(IFS=" " ; set -- $string ; echo $1)Configure IFS para reconocer correctamente el espacio entre las palabras. Envuelva entre paréntesis para evitar golpear el contenido original de $ 1.
Steve Pitchers
Esto está roto ya que está sujeto a la expansión del nombre de ruta. Prueba con string="*". Sorpresa.
gniourf_gniourf
32

Creo que una forma eficiente es el uso de matrices bash:

array=( $string ) # do not use quotes in order to allow word expansion
echo ${array[0]}  # You can retrieve any word. Index runs from 0 to length-1

Además, puede leer directamente matrices en una tubería:

echo "word1 word2" | while read -a array; do echo "${array[0]}" ; done
Isaías
fuente
1
echo " word1 word2 " | { read -a array ; echo ${array[0]} ; }
Boontawee Home
Esto está roto ya que está sujeto a la expansión del nombre de ruta. Prueba con string="*". Sorpresa.
gniourf_gniourf
Use la whilesintaxis para recuperar cada primera palabra en cada línea. De lo contrario, utilice el enfoque Boontawee Home. Además, tenga en cuenta que echo "${array[0]}"se ha citado para evitar la expansión como lo notó gniourf-gniourf.
Isaías
Si intenta acceder a un índice de matriz que es mayor que la cantidad de palabras, no obtendrá un error. Usted acaba de conseguir una línea vacía
Dhumil Agarwal
26
echo "word1 word2 word3" | { read first rest ; echo $first ; }

Esto tiene la ventaja de que no utiliza comandos externos y deja intactas las variables $ 1, $ 2, etc.

John Marter
fuente
¡Dejar las variables $1, $2, …intactas es una característica extremadamente útil para escribir guiones!
Serge Stroobandt
14

Si está seguro de que no hay espacios iniciales, puede usar la sustitución de parámetros bash:

$ string="word1  word2"
$ echo ${string/%\ */}
word1

Cuidado con escapar del espacio único. Vea aquí para más ejemplos de patrones de sustitución. Si tiene bash> 3.0, también puede usar la coincidencia de expresiones regulares para hacer frente a los espacios iniciales: consulte aquí :

$ string="  word1   word2"
$ [[ ${string} =~ \ *([^\ ]*) ]]
$ echo ${BASH_REMATCH[1]}
word1
dsl101
fuente
11

Podrías probar awk

echo "word1 word2" | awk '{ print $1 }'

Con awk es realmente fácil elegir cualquier palabra que desee ($ 1, $ 2, ...)

mfloryan
fuente
11

Usar la expansión de parámetros de shell %% *

Aquí hay otra solución que usa la expansión de parámetros de shell . Se ocupa de múltiples espacios después de la primera palabra. El manejo de espacios delante de la primera palabra requiere una expansión adicional.

string='word1    word2'
echo ${string%% *}
word1

string='word1    word2      '
echo ${string%% *}
word1

Explicación

Las %%significa borrar la coincidencia más larga de  *(un espacio seguido de cualquier número de cualesquiera otros caracteres) en el arrastre parte de string.

Serge Stroobandt
fuente
9

Me preguntaba cómo se midieron varias de las mejores respuestas en términos de velocidad. Probé lo siguiente:

1 @ mattbh

echo "..." | awk '{print $1;}'

2 @ ghostdog74's

string="..."; set -- $string; echo $1

3 @ boontawee-home's

echo "..." | { read -a array ; echo ${array[0]} ; }

y 4 @ boontawee-home's

echo "..." | { read first _ ; echo $first ; }

Los medí con el timeit de Python en un script Bash en un terminal Zsh en macOS, usando una cadena de prueba con 215 palabras de 5 letras. Hizo cada medición cinco veces (los resultados fueron todos para 100 bucles, el mejor de 3), y promediaron los resultados:

method       time
--------------------------------
1. awk       9.2ms
2. set       11.6ms (1.26 * "1")
3. read -a   11.7ms (1.27 * "1")
4. read      13.6ms (1.48 * "1")

Buen trabajo, votantes 👏 ¡Los votos (a partir de este escrito) coinciden con la velocidad de las soluciones!

Enrique
fuente
Es extraño que puedas medir 3 en el tablero, ya que el tablero no admite matrices ( read -ano es válido en el tablero).
gniourf_gniourf
Sí, eso es raro Lo descarté, hice las pruebas de velocidad, luego pensé "¿por qué lo dejé?" Y lo agregué. Eliminándolo ahora, y puedo volver a ejecutar las cosas más tarde para asegurarme de que no tuve ningún error
Henry
6
echo "word1 word2" | cut -f 1 -d " "

cortar corta el primer campo (-f 1) de una lista de campos delimitados por la cadena "" (-d "")

lajuette
fuente
eso es una manera, pero su estado de corte no va a distinguir múltiples espacios entre palabras, si quiere conseguir palabra2 más adelante
ghostdog74
sí, la solución awk es la mejor.
lajuette
3

read es tu amigo:

  • Si la cadena está en una variable:

    string="word1 word2"
    read -r first _ <<< "$string"
    printf '%s\n' "$first"
  • Si está trabajando en una tubería: primer caso: solo desea la primera palabra de la primera línea:

    printf '%s\n' "word1 word2" "line2" | { read -r first _; printf '%s\n' "$first"; }

    segundo caso: desea la primera palabra de cada línea:

    printf '%s\n' "word1 word2" "worda wordb" | while read -r first _; do printf '%s\n' "$first"; done

Estos funcionan si hay espacios principales:

printf '%s\n' "   word1 word2" | { read -r first _; printf '%s\n' "$first"; }
gniourf_gniourf
fuente
0

Como perl incorpora la funcionalidad de awk, esto también se puede resolver con perl:

echo " word1 word2" | perl -lane 'print $F[0]'
tssch
fuente
0

Estaba trabajando con un dispositivo incrustado que no tenía ni perl, awk ni python y lo hizo con sed en su lugar. Admite múltiples espacios antes de la primera palabra (que las soluciones cuty bashno manejaron).

VARIABLE="  first_word_with_spaces_before_and_after  another_word  "
echo $VARIABLE | sed 's/ *\([^ ]*\).*/\1/'

Esto fue muy útil al buscar psidentificadores de proceso ya que las otras soluciones aquí que usan solo bash no pudieron eliminar los primeros espacios que se psusan para alinear.

Johan Bjäreholt
fuente