bash: sortie séparée de la commande par colonnes
Question
Je veux faire ceci:
- lancer une commande
- capturer la sortie
- sélectionnez une ligne
- sélectionnez une colonne de cette ligne
Juste à titre d'exemple, supposons que je souhaite obtenir le nom de la commande à partir d'un $ PID
(veuillez noter qu'il ne s'agit que d'un exemple, je ne suggère pas qu'il s'agit du moyen le plus simple d'obtenir un nom de la commande d'un identifiant de processus - mon vrai problème est avec une autre commande dont je ne peux pas contrôler le format de sortie).
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, je fais ps | egrep 11383
et obtenez
11383 pts/1 00:00:00 bash
Étape suivante: ps | egrep 11383 | couper-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 la ressemblance d'un tableau, < code> cut sélectionne une chaîne vide. Bien sûr, je pourrais utiliser cut
pour sélectionner le 7ème et non le 4ème champ, mais comment puis-je le savoir, en particulier lorsque la sortie est variable et inconnue au préalable.
La solution
Une solution simple consiste à ajouter un code tr
pour extraire les séparateurs de champs répétés:
$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4
Autres conseils
Je pense que le moyen le plus simple consiste à utiliser awk . Exemple:
$ echo "11383 pts/1 00:00:00 bash" | awk '{ print $4; }'
bash
Veuillez noter que l'option tr -s ''
"ne supprimera aucun espace précédant unique. 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
Ensuite, la découpe donnera une ligne vide 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 du shell
Cependant, en général, il est toujours possible d'utiliser les fonctions du 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 et s'il y en a plus, tout le reste sera placé dans b
. . Par conséquent, 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 alors 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 multidisque
Nous aboutissons alors à 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 compétences en 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 le cas; -).
essayer
ps |&
while read -p first second third fourth etc ; do
if [[ $first == '11383' ]]
then
echo got: $fourth
fi
done
Semblable à la solution awk de brianegge, voici l'équivalent Perl:
ps | egrep 11383 | perl -lane 'print $F[3]'
-a
active le mode autosplit, qui remplit le tableau @F
avec les données de colonne.
Utilisez -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
Obtenir la bonne ligne (exemple pour la ligne n ° 6) est fait avec tête et queue et le mot correct (mot n ° 4) peut être capturé avec awk:
command|head -n 6|tail -n 1|awk '{print $4}'
Utilisation de variables de tableau
set $(ps | egrep "^11383 "); echo $4
ou
A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}
Au lieu de faire tous ces greps et ce genre de 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.
Votre commande
ps | egrep 11383 | cut -d" " -f 4
manque un tr -s
pour serrer 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 en 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
s'il apparaît au début de la ligne, vous pouvez dire ps | awk '/ ^ 11383 / {print $ 4}'
.
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:"