bash: divide la salida del comando por columnas
Pregunta
Quiero hacer esto:
- ejecuta un comando
- captura la salida
- seleccione una línea
- seleccione una columna de esa línea
Solo como ejemplo, digamos que quiero obtener el nombre del comando de un $ PID
(tenga en cuenta que esto es solo un ejemplo, no estoy sugiriendo que esta sea la forma más fácil de obtener un nombre de comando de una identificación de proceso: mi problema real es con otro comando cuyo formato de salida no puedo controlar).
Si ejecuto ps
obtengo:
PID TTY TIME CMD
11383 pts/1 00:00:00 bash
11771 pts/1 00:00:00 ps
Ahora hago ps | egrep 11383
y obtener
11383 pts/1 00:00:00 bash
Siguiente paso:
<absolutely nothing/>
El problema es que cut
corta la salida en espacios individuales, y como ps
agrega algunos espacios entre las columnas segunda y tercera para mantener cierto parecido con una tabla, < code> cut selecciona una cadena vacía. Por supuesto, podría usar cut
para seleccionar el séptimo y no el cuarto campo, pero ¿cómo puedo saberlo, especialmente cuando el resultado es variable y desconocido de antemano?
Solución
Una manera fácil es agregar un pase de tr
para exprimir los separadores de campo repetidos:
$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4
Otros consejos
Creo que la forma más sencilla es usar awk . Ejemplo:
$ echo "11383 pts/1 00:00:00 bash" | awk '{ print $4; }'
bash
Tenga en cuenta que la opción tr -s ''
no eliminará ningún espacio inicial único. Si su columna está alineada a la derecha (como con ps
pid) ...
$ ps h -o pid,user -C ssh,sshd | tr -s " "
1543 root
19645 root
19731 root
Luego, el corte dará como resultado una línea en blanco para algunos de esos campos si es la primera columna:
$ <previous command> | cut -d ' ' -f1
19645
19731
A menos que lo preceda con un espacio, obviamente
$ <command> | sed -e "s/.*/ &/" | tr -s " "
Ahora, para este caso particular de números pid (no nombres), hay una función llamada pgrep
:
$ pgrep ssh
Funciones de shell
Sin embargo, en general, todavía es posible usar funciones de shell de manera concisa, porque hay algo interesante sobre el comando read
:
$ <command> | while read a b; do echo $a; done
El primer parámetro a leer, a
, selecciona la primera columna, y si hay más, todo lo demás se colocará en b
. Como resultado, nunca necesita más variables que el número de su columna +1 .
Entonces,
while read a b c d; do echo $c; done
mostrará la tercera columna. Como se indica en mi comentario ...
Una lectura canalizada se ejecutará en un entorno que no pase variables al script de llamada.
out=$(ps whatever | { read a b c d; echo $c; })
arr=($(ps whatever | { read a b c d; echo $c $b; }))
echo ${arr[1]} # will output 'b'`
La solución de matriz
Entonces terminamos con la respuesta de @frayser que es usar la variable de shell IFS que por defecto es un espacio, para dividir la cadena en una matriz. Sin embargo, solo funciona en Bash. Dash y Ash no lo admiten. Me ha costado mucho dividir una cadena en componentes en una cosa de Busybox. Es bastante fácil obtener un solo componente (por ejemplo, usando awk) y luego repetirlo para cada parámetro que necesite. Pero luego terminas llamando repetidamente a awk en la misma línea, o usando repetidamente un bloque de lectura con eco en la misma línea. Lo cual no es eficiente ni bonito. Entonces terminas dividiéndote usando $ {name %% *}
y así sucesivamente. Te hace añorar algunas habilidades de Python porque, de hecho, las secuencias de comandos de shell ya no son muy divertidas si la mitad o más de las funciones a las que estás acostumbrado desaparecen. Pero puede suponer que incluso Python no se instalaría en dicho sistema, y ??no fue así ;-).
prueba
ps |&
while read -p first second third fourth etc ; do
if [[ $first == '11383' ]]
then
echo got: $fourth
fi
done
Similar a la solución awk de brianegge, aquí está el equivalente de Perl:
ps | egrep 11383 | perl -lane 'print $F[3]'
-a
habilita el modo de división automática, que llena la matriz @F
con los datos de la columna.
Utilice -F,
si sus datos están delimitados por comas, en lugar de delimitados por espacios.
El campo 3 se imprime ya que Perl comienza a contar desde 0 en lugar de 1
Obtener la línea correcta (ejemplo para la línea no. 6) se realiza con cabeza y cola y la palabra correcta (palabra no. 4) se puede capturar con awk:
command|head -n 6|tail -n 1|awk '{print $4}'
Uso de variables de matriz
set $(ps | egrep "^11383 "); echo $4
o
A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}
En lugar de hacer todos estos greps y cosas, te aconsejo que uses las capacidades ps para cambiar el formato de salida.
ps -o cmd= -p 12345
Obtiene la línea cmmand de un proceso con el pid especificado y nada más.
Esto es compatible con POSIX y, por lo tanto, puede considerarse portátil.
Tu comando
ps | egrep 11383 | cut -d" " -f 4
pierde un tr -s
para exprimir espacios, como explica el desenrollado en su respuesta .
Sin embargo, es posible que desee utilizar awk
, ya que maneja todas estas acciones en un solo comando:
ps | awk '/11383/ {print $4}'
Esto imprime la cuarta columna en esas líneas que contienen 11383
. Si desea que esto coincida con 11383
si aparece al comienzo de la línea, puede decir ps | awk '/ ^ 11383 / {print $ 4}'
.
conjunto de Bash
analizará toda la salida en parámetros de posición.
Por ejemplo, con el comando set $ (free -h)
, echo $ 7
mostrará " Mem: "