web-dev-qa-db-fra.com

bash: sortie séparée de la commande par colonnes

Je veux faire ça:

  1. lancer une commande
  2. capturer la sortie
  3. sélectionner une ligne
  4. sélectionner une colonne de cette ligne

Juste à titre d'exemple, disons que je veux obtenir le nom de la commande d'un $PID _ (veuillez noter qu'il ne s'agit que d'un exemple. Je ne suggère pas qu'il s'agisse du moyen le plus simple d'obtenir le nom d'une commande à partir d'un identifiant de processus. Mon problème est une autre commande dont le format de sortie est incontrôlable).

Si je lance ps je reçois:


  PID TTY          TIME CMD
11383 pts/1    00:00:00 bash
11771 pts/1    00:00:00 ps

Maintenant oui ps | egrep 11383 et obtenir

11383 pts/1    00:00:00 bash

L'étape suivante: ps | egrep 11383 | cut -d" " -f 4. La sortie est:

<absolutely nothing/>

Le problème est que cut coupe la sortie par des espaces simples et que ps ajoute des espaces entre les deuxième et troisième colonnes pour conserver l’apparence d’une table, cut sélectionne un chaîne vide. Bien sûr, je pourrais utiliser cut pour sélectionner le 7ème champ et non le 4ème champ, mais comment puis-je le savoir, en particulier lorsque la sortie est variable et inconnue au préalable.

77
flybywire

Un moyen simple consiste à ajouter une passe de tr pour faire sortir tous les séparateurs de champs répétés:

$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4
153
unwind

Je pense que le moyen le plus simple est d'utiliser awk. Exemple:

$ echo "11383 pts/1    00:00:00 bash" | awk '{ print $4; }'
bash
62
brianegge

Veuillez noter que l'option tr -s ' ' Ne supprimera aucun espace en tête. Si votre colonne est alignée à droite (comme avec ps pid) ...

$ ps h -o pid,user -C ssh,sshd | tr -s " "
 1543 root
19645 root
19731 root

Si vous coupez, une ligne vide apparaît pour certains de ces champs s'il s'agit de la première colonne:

$ <previous command> | cut -d ' ' -f1

19645
19731

Sauf si vous le faites précéder d'un espace, évidemment

$ <command> | sed -e "s/.*/ &/" | tr -s " "

Maintenant, pour ce cas particulier de nombres pid (pas de noms), il existe une fonction appelée pgrep:

$ pgrep ssh


Fonctions shell

Cependant, en général, il est encore possible d'utiliser fonctions de shell de manière concise, car la commande read a un intérêt particulier:

$ <command> | while read a b; do echo $a; done

Le premier paramètre à lire, a, sélectionne la première colonne. S'il y en a plus, , tout le reste sera placé dans b. En conséquence, vous n’avez jamais besoin de plus de variables que le nombre de votre colonne + 1 .

Alors,

while read a b c d; do echo $c; done

affichera ensuite la 3ème colonne. Comme indiqué dans mon commentaire ...

Une lecture canalisée sera exécutée dans un environnement qui ne transmet pas de variables au script appelant.

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 solution de tableau

Nous nous retrouvons donc avec la réponse de @frayser qui consiste à utiliser la variable Shell IFS qui utilise par défaut un espace pour scinder la chaîne en un tableau. Cela ne fonctionne que dans Bash cependant. Dash et Ash ne le supportent pas. J'ai eu beaucoup de mal à scinder une chaîne en composants dans une boîte de dialogue Busybox. Il est assez facile d’obtenir un seul composant (par exemple, en utilisant awk), puis de le répéter pour chaque paramètre dont vous avez besoin. Mais vous finissez par appeler awk à plusieurs reprises sur la même ligne ou en utilisant à plusieurs reprises un bloc de lecture avec écho sur la même ligne. Ce qui n'est ni efficace ni joli. Vous finissez donc par scinder en utilisant ${name%% *} Et ainsi de suite. Vous avez envie de certaines Python car en fait, les scripts Shell ne sont plus très amusants si la moitié ou plus des fonctionnalités auxquelles vous êtes habitué ont disparu. Mais vous pouvez supposer que même = python ne serait pas installé sur un tel système, et ce n'était pas ;-).

10
Xennex81

essayer

ps |&
while read -p first second third fourth etc ; do
   if [[ $first == '11383' ]]
   then
       echo got: $fourth
   fi       
done
3
James Anderson

Semblable à la solution awk de brianegge, voici l'équivalent de Perl:

ps | egrep 11383 | Perl -lane 'print $F[3]'

-a active le mode autosplit, qui renseigne le @F tableau avec les données de la colonne.
Utilisation -F, si vos données sont délimitées par des virgules plutôt que par des espaces.

Le champ 3 est imprimé car Perl commence à compter à partir de 0 au lieu de 1

2
Chris Koknat

Utiliser des variables de tableau

set $(ps | egrep "^11383 "); echo $4

ou

A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}
1
frayser

Obtenir la bonne ligne (exemple pour la ligne n ° 6) est fait avec la tête et la queue et le mot correct (Word n ​​° 4) peut être capturé avec awk:

command|head -n 6|tail -n 1|awk '{print $4}'
1
soulmerge

Votre commande

ps | egrep 11383 | cut -d" " -f 4

manque un tr -s pour presser les espaces, comme l'explique le déroulement dans sa réponse .

Cependant, vous voudrez peut-être utiliser awk, car il gère toutes ces actions dans une seule commande:

ps | awk '/11383/ {print $4}'

Ceci affiche la 4ème colonne de ces lignes contenant 11383. Si vous voulez que cela corresponde à 11383 si elle apparaît au début de la ligne, vous pouvez dire ps | awk '/^11383/ {print $4}'.

0
fedorqui

Le set de Bash analysera toutes les sorties dans les paramètres de position.

Par exemple, avec la commande set $(free -h), echo $7 affichera "Mem:"

0
dman

Au lieu de faire tous ces greps et d'autres choses, je vous conseillerais d'utiliser les fonctionnalités ps de changement de format de sortie.

ps -o cmd= -p 12345

Vous obtenez la ligne cmmand d'un processus avec le pid spécifié et rien d'autre.

Ceci est conforme à POSIX et peut donc être considéré comme portable.

0
P Shved