bash: la forma más corta de obtener la enésima columna de salida

165

Digamos que durante su jornada laboral se encuentra repetidamente la siguiente forma de salida en columna de algún comando en bash (en mi caso, al ejecutar svn sten mi directorio de trabajo de Rails):

?       changes.patch
M       app/models/superman.rb
A       app/models/superwoman.rb

Para trabajar con la salida de su comando, en este caso los nombres de archivo, se requiere algún tipo de análisis para que la segunda columna se pueda usar como entrada para el siguiente comando.

Lo que he estado haciendo es usar awkpara llegar a la segunda columna, por ejemplo, cuando quiero eliminar todos los archivos (no es que sea un caso de uso típico :), haría:

svn st | awk '{print $2}' | xargs rm

Como escribo mucho esto, una pregunta natural es: ¿hay una forma más corta (por lo tanto, más genial) de lograr esto en bash?

NOTA: Lo que estoy preguntando es esencialmente una pregunta de comando de shell a pesar de que mi ejemplo concreto está en mi flujo de trabajo svn. Si cree que el flujo de trabajo es tonto y sugiere un enfoque alternativo, probablemente no lo rechazaré, pero otros podrían hacerlo, ya que la pregunta aquí es realmente cómo obtener la salida del comando de la enésima columna en bash, de la manera más breve posible . Gracias :)

Sv1
fuente
1
Cuando usa un comando a menudo, es mejor crear un script y ponerlo en su camino. Simplemente puede crear una función en su bashrc si lo desea. No veo el punto de reducir la expresión de selección de columna.
Lynch
1
Tienes razón, y podría hacer eso. El 'punto' es la búsqueda de nuevas formas de hacer cosas en bash, con el propósito de aprender pero sobre todo por diversión :)
Sv1
1
Además, no tienes tu .bashrc cuando ssh-shing en algún lugar, por lo que es útil saber cómo moverse sin él.
Kos

Respuestas:

125

Puede usar cutpara acceder al segundo campo:

cut -f2

Editar: Lo siento, no me di cuenta de que SVN no usa pestañas en su salida, por lo que es un poco inútil. Puede personalizar cutla salida, pero es un poco frágil, algo así cut -c 10- funcionaría, pero el valor exacto dependerá de su configuración.

Otra opción es algo como: sed 's/.\s\+//'

porges
fuente
1
Intenta usar cut -f2con la svn stsalida. Verás que no funciona.
Lynch
Supuse que lo real svn stusaría pestañas en su salida. Después de algunas pruebas, parece que este no es el caso. Maldición.
porges
21
Pasar un delimitador apropiado para -dpermitir que esto funcione.
Yogh
66
Para ampliar lo que dijo @Yogh, para espacios como delimitador, se vería como cut -d" " -f2 .
adamyonk
Sin awk en stdout use xargs: svn st | xargs | cut -d "" -f2
vr286
106

Para lograr lo mismo que:

svn st | awk '{print $2}' | xargs rm

usando solo bash puedes usar:

svn st | while read a b; do rm "$b"; done

De acuerdo, no es más corto, pero es un poco más eficiente y maneja los espacios en blanco en sus nombres de archivo correctamente.

Rick Sladkey
fuente
¿Qué es ay bcómo conseguirlos?
Timo
1
@Timo arepresenta la primera columna y brepresenta las columnas restantes. Si desea imprimir la segunda columna, use read a b c;y luego use en echolugar de rm. Utilicé esto para obtener un montón de procesos de ID que seguían un patrón grep, para poder interrumpirlos a todos.
Matt Kleinsmith
36

Me encontré en la misma situación y terminé agregando estos alias a mi .profilearchivo:

alias c1="awk '{print \$1}'"
alias c2="awk '{print \$2}'"
alias c3="awk '{print \$3}'"
alias c4="awk '{print \$4}'"
alias c5="awk '{print \$5}'"
alias c6="awk '{print \$6}'"
alias c7="awk '{print \$7}'"
alias c8="awk '{print \$8}'"
alias c9="awk '{print \$9}'"

Lo que me permite escribir cosas como esta:

svn st | c2 | xargs rm
Apilado
fuente
15
Las funciones Bash suelen ser más útiles. Hice esto: function c() { awk "{print \$$1}" } Entonces puedes hacer: svn st | c 2 | xargs rm
Aissen
8
Pero luego necesito escribir un espacio extra. Eso es demasiado agotador :)
StackedCrooked
16

Prueba el zsh. Admite alias de sufijo, por lo que puede definir X en su .zshrc para que sea

alias -g X="| cut -d' ' -f2"

entonces puedes hacer:

cat file X

Puede ir un paso más allá y definirlo para la enésima columna:

alias -g X2="| cut -d' ' -f2"
alias -g X1="| cut -d' ' -f1"
alias -g X3="| cut -d' ' -f3"

que generará la enésima columna del archivo "archivo". También puede hacer esto para la salida grep o menos salida. Esto es muy útil y una característica asesina de la zsh.

Puede ir un paso más allá y definir que D sea:

alias -g D="|xargs rm"

Ahora puedes escribir:

cat file X1 D

para eliminar todos los archivos mencionados en la primera columna del archivo "archivo".

Si conoce el bash, el zsh no es un gran cambio, excepto por algunas características nuevas.

HTH Chris

Chris
fuente
ahh, veo que has actualizado tu respuesta mientras escribía mi comentario anterior :) ¿Puedes de alguna manera especificar dinámicamente qué columna obtener, o necesitaría líneas n en mi .zshrc (no es que sea importante, solo curiosidad)
Sv1
Edité mi publicación más y definí un sufijo "D" para eliminar archivos. Hasta donde sé, tendrá que agregar una línea para cada sufijo.
Chris
O conviértalo en el primer parámetro del comando X. Nada de esto requiere zsh o alias, excepto quizás por la peculiar idea de poner una tubería en un alias.
tripleee
1
No entiendo por qué a todos parece gustarles la cutopción. Simplemente no funciona en mi máquina usando la salida de svn st. Intenta svn st | cut -d' ' -f2solo ver qué sucede.
Lynch
@ Sv1 podría escribir un bucle para definir los alias, ya que alias es solo un comando.
driftcatcher
8

Como parece no estar familiarizado con los scripts, aquí hay un ejemplo.

#!/bin/sh
# usage: svn st | x 2 | xargs rm
col=$1
shift
awk -v col="$col" '{print $col}' "${@--}"

Si guarda esto ~/bin/xy se asegura de que ~/binesté en su PATH(ahora eso es algo que puede y debe poner en su.bashrc ), tiene el comando más corto posible para extraer generalmente la columna n; x n.

La secuencia de comandos debe realizar una verificación de errores adecuada y salir bajo fianza si se invoca con un argumento no numérico o el número incorrecto de argumentos, etc. pero la expansión de esta versión esencial básica estará en la unidad 102.

Tal vez desee extender el script para permitir un delimitador de columna diferente. Awk analiza de forma predeterminada la entrada en los campos en el espacio en blanco; para usar un delimitador diferente, use -F ':'where :es el nuevo delimitador. Implementar esto como una opción para el script lo hace un poco más largo, así que lo dejo como un ejercicio para el lector.


Uso

Dado un archivo file:

1 2 3
4 5 6

Puede pasarlo a través de stdin (usando un inútilcat simplemente como marcador de posición para algo más útil);

$ cat file | sh script.sh 2
2
5

O proporcionarlo como un argumento para el script:

$ sh script.sh 2 file
2
5

Aquí, sh script.shse supone que el script se guarda como script.shen el directorio actual; si lo guarda con un nombre más útil en algún lugar de su PATHy lo marca como ejecutable, como en las instrucciones anteriores, obviamente use el nombre útil en su lugar (y no sh).

tripleee
fuente
Buena idea, pero no funciona para mí, ya que está en sh o bash. Esto funciona: #! / Bin / bash col = $ 1 shift awk "{print \ $$ col}"
mgk
Gracias por este comentario tardío. Actualizado con su corrección.
tripleee
1
mejorawk -v col=$col ...
fedorqui 'SO deja de dañar'
@fedorqui ¡Gracias por tus comentarios, aquí y en otros lugares! Se actualizó la respuesta. Ahora es un poco más largo, por lo que si desea el script absolutamente más corto que pueda tener, consulte el historial de revisiones de la versión original.
tripleee
1
@fedorqui Muchas gracias! Se agregó una pequeña advertencia al catejemplo por razones histéricas (-:
tripleee el
5

Parece que ya tienes una solución. Para facilitar las cosas, ¿por qué no simplemente poner su comando en un script bash (con un nombre corto) y simplemente ejecutarlo en lugar de escribir ese comando 'largo' cada vez?

Tarek Fadel
fuente
Es solo que no es tan 'genial' en mi opinión. ¿Por qué? Bueno, no quiero tener que inundar mi .bashrc con varios accesos directos que hacen esto y aquello, esencialmente escribiendo un meta-shell. Es bastante alias como es. Dicho esto, su sugerencia no es mala.
Sv1
2
.bashrc? ¿Por qué lo abarrotaría con cosas no relacionadas con bash? Lo que hago es escribir scripts individuales para cada 'conjunto' de comandos y ponerlos todos en un ~/scriptsdirectorio. Luego agregue todo ~/scriptsa mi PATHpara que pueda llamarlos por su nombre cuando los necesite.
Tarek Fadel
44
Entonces no lo pongas en tu .bashrc. Cree un script en ~ / bin y asegúrese de que esté en su RUTA.
tripleee
2

Si está de acuerdo con la selección manual de la columna, puede ser muy rápido usando pick :

svn st | pick | xargs rm

Simplemente vaya a cualquier celda de la segunda columna, presione cy luego presioneenter

Bernardo Rufino
fuente
Probé la herramienta de "selección" a la que hiciste referencia, y me gusta mucho. Es muy bueno para muchas situaciones en las que quiero imprimir columnas seleccionadas pero no quiero escribir "awk '{print $ 3}'" solo para obtener la columna deseada. Desafortunadamente, el nombre entra en conflicto con una herramienta diferente de "selección" que parece ser más popular: se puede instalar con "apt install pick". Así que cambié el nombre de su herramienta a "pickk" en mi sistema y tengo la intención de seguir usándola. Gracias por publicar una referencia.
William Dye
1

Tenga en cuenta que la ruta del archivo no tiene que estar en la segunda columna de salida de svn st. Por ejemplo, si modifica el archivo y modifica su propiedad, será la tercera columna.

Ver posibles ejemplos de salida en:

svn help st

Salida de ejemplo:

 M     wc/bar.c
A  +   wc/qax.c

Sugiero cortar los primeros 8 caracteres por:

svn st | cut -c8- | while read FILE; do echo whatever with "$FILE"; done

Si desea estar 100% seguro y, por ejemplo, tratar con nombres de archivo elegantes con espacios en blanco al final, debe analizar la salida xml:

svn st --xml | grep -o 'path=".*"' | sed 's/^path="//; s/"$//'

Por supuesto, es posible que desee utilizar un analizador XML real en lugar de grep / sed.

Michał Šrajer
fuente