Quiero hacer esto:
- ejecutar un comando
- capturar la salida
- seleccionar una línea
- seleccione una columna de esa línea
Solo como ejemplo, digamos que quiero obtener el nombre de 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 un ID de proceso; mi problema real es con otro comando cuyo formato de salida no puedo controlar).
Si corro ps
obtengo:
PID TTY TIME CMD
11383 pts/1 00:00:00 bash
11771 pts/1 00:00:00 ps
Ahora lo hago ps | egrep 11383
y consigo
11383 pts/1 00:00:00 bash
Paso siguiente: ps | egrep 11383 | cut -d" " -f 4
. La salida es:
<absolutely nothing/>
El problema es que cut
corta la salida por espacios simples, y como ps
agrega algunos espacios entre la 2da y 3ra columna para mantener cierta semejanza con una tabla, cut
elige 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 la salida es variable y desconocida de antemano.
Respuestas:
Una forma fácil es agregar un pase de
tr
para exprimir cualquier separador de campo repetido:$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4
fuente
tr
es más ligero queawk
Creo que la forma más sencilla es usar awk . Ejemplo:
$ echo "11383 pts/1 00:00:00 bash" | awk '{ print $4; }' bash
fuente
ps | awk "\$1==$PID{print\$4}"
o (mejor)ps | awk -v"PID=$PID" '$1=PID{print$4}'
. Por supuesto, en Linux simplemente podría hacerxargs -0n1 </proc/$PID/cmdline | head -n1
oreadlink /proc/$PID/exe
, pero de todos modos ...;
de{ print $4; }
necesaria? Eliminarlo parece no tener ningún efecto para mí en Linux, solo tengo curiosidad por su propósitoTenga en cuenta que la
tr -s ' '
opción no eliminará ningún espacio inicial. Si su columna está alineada a la derecha (como conps
pid) ...$ ps h -o pid,user -C ssh,sshd | tr -s " " 1543 root 19645 root 19731 root
Luego, cortar 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
:Funciones de shell
Sin embargo, en general, todavía es posible usar funciones de shell de manera concisa, porque hay algo interesante sobre el
read
comando:$ <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, se incluirá todo lo demásb
. 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
luego generará la tercera columna. Como se indica en mi comentario ...
Una lectura canalizada se ejecutará en un entorno que no pasa 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 apoyan. Me ha costado mucho dividir una cadena en componentes en un 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í. Te hace anhelar algunas habilidades de Python porque, de hecho, el script de shell ya no es muy divertido si la mitad o más de las funciones a las que estás acostumbrado se han ido. Pero puede asumir que incluso Python no se instalaría en un sistema de este tipo, y no lo fue ;-).fuente
echo "$a"
yecho "$c"
aunque.var=$(....... | { read a b c d; echo $c; })
. Eso solo funciona para una sola (cadena), aunque en Bash puede dividirla en una matriz usandoar=($var)
tratar
ps |& while read -p first second third fourth etc ; do if [[ $first == '11383' ]] then echo got: $fourth fi done
fuente
Usando variables de matriz
set $(ps | egrep "^11383 "); echo $4
o
A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}
fuente
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@F
matriz con los datos de la columna.Úselo
-F,
si sus datos están delimitados por comas, en lugar de por espacios.El campo 3 se imprime ya que Perl comienza a contar desde 0 en lugar de 1
fuente
Obtener la línea correcta (ejemplo para la línea n. ° 6) se hace con head y tail y la palabra correcta (palabra n. ° 4) se puede capturar con awk:
command|head -n 6|tail -n 1|awk '{print $4}'
fuente
awk NR=6 {print $4}
sería un poco más eficienteawk NR==6 {print $4}
* doh *Tu mando
ps | egrep 11383 | cut -d" " -f 4
echa de menos un
tr -s
para apretar espacios, como lo explica relajarse 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 las líneas que contienen
11383
. Si desea que esto coincida11383
si aparece al principio de la línea, puede decirps | awk '/^11383/ {print $4}'
.fuente
En lugar de hacer todos estos greps y esas cosas, le aconsejo que utilice las capacidades ps para cambiar el formato de salida.
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.
fuente
Bash
set
analizará toda la salida en parámetros de posición.Por ejemplo, con el
set $(free -h)
comando,echo $7
mostrará "Mem:"fuente
set $(sar -r 1 1)
;echo "${23}"
awk
es la mejor manera de hacerlo.bash
y noawk
.