Ausgabe des Befehls mit Bash nach Spalten teilen?

85

Ich möchte das machen:

  1. Führen Sie einen Befehl aus
  2. Erfassen Sie die Ausgabe
  3. Wählen Sie eine Zeile
  4. Wählen Sie eine Spalte dieser Zeile aus

Nehmen wir als Beispiel an, ich möchte den Befehlsnamen von a erhalten $PID(bitte beachten Sie, dass dies nur ein Beispiel ist. Ich schlage nicht vor, dass dies der einfachste Weg ist, einen Befehlsnamen von einer Prozess-ID zu erhalten - mein eigentliches Problem ist mit ein anderer Befehl, dessen Ausgabeformat ich nicht steuern kann).

Wenn ich renne, psbekomme ich:


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

Jetzt mache ich ps | egrep 11383und bekomme

11383 pts/1    00:00:00 bash

Nächster Schritt : ps | egrep 11383 | cut -d" " -f 4. Ausgabe ist:

<absolutely nothing/>

Das Problem besteht darin, dass cutdie Ausgabe um einzelne Leerzeichen gekürzt wird und beim psHinzufügen einiger Leerzeichen zwischen der 2. und 3. Spalte cuteine leere Zeichenfolge ausgewählt wird , um die Ähnlichkeit mit einer Tabelle beizubehalten. Natürlich könnte ich gebrauchencut das 7. und nicht das 4. Feld auswählen, aber wie kann ich wissen, insbesondere wenn die Ausgabe vorher variabel und unbekannt ist.

Flybywire
quelle
2
Verwenden Sie awk (und 25 weitere Zeichen).
Michael Foukarakis

Antworten:

174

Eine einfache Möglichkeit besteht darin, einen Durchgang hinzuzufügen tr, um wiederholte Feldtrennzeichen herauszudrücken:

$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4
entspannen
quelle
1
Ich mag dieses, sieht aus wie tres leichter ist alsawk
Flybywire
3
Ich würde eher zustimmen, aber das könnte auch daran liegen, dass ich awk nicht gelernt habe. :)
Entspannen Sie
Funktioniert nicht, wenn Sie zufällig einen Prozess mit PID haben, der die PID enthält, an der Sie als Subtring interessiert sind.
David Grayson
1
Außerdem sind die Feldnonnen ausgeschaltet, wenn einige PIDs links mit Platz gepolstert sind, andere nicht.
Tripleee
67

Ich denke, der einfachste Weg ist zu verwenden awk . Beispiel:

$ echo "11383 pts/1    00:00:00 bash" | awk '{ print $4; }'
bash
Brianegge
quelle
4
Zur Kompatibilität mit der ursprünglichen Frage ps | awk "\$1==$PID{print\$4}"oder (besser) ps | awk -v"PID=$PID" '$1=PID{print$4}'. Natürlich könnte man unter Linux einfach machen xargs -0n1 </proc/$PID/cmdline | head -n1oder readlink /proc/$PID/exe, aber trotzdem ...
kurzlebig
Ist das ;in { print $4; }erforderlich? Das Entfernen scheint für mich unter Linux keine Auswirkungen zu haben, nur neugierig auf den Zweck
igniteflow
@igniteflow würde es nicht das Ende des Befehls anzeigen, wenn Sie nach der print-Anweisung weiter hinzufügen möchten?
Joshmcode
15

Bitte beachten Sie, dass mit dieser tr -s ' 'Option keine einzelnen führenden Leerzeichen entfernt werden. Wenn Ihre Spalte rechtsbündig ist (wie bei pspid) ...

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

Wenn Sie die erste Spalte verwenden, wird für einige dieser Felder eine leere Zeile angezeigt:

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

19645
19731

Es sei denn, Sie setzen natürlich ein Leerzeichen voran

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

Für diesen speziellen Fall von PID-Nummern (nicht Namen) gibt es eine Funktion namens pgrep:

$ pgrep ssh


Shell-Funktionen

Im Allgemeinen ist es jedoch immer noch möglich, Shell-Funktionen präzise zu verwenden, da der readBefehl eine nette Sache enthält :

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

Der erste zu lesende Parameter awählt die erste Spalte aus, und wenn mehr vorhanden ist, wird alles andere eingegeben b. Daher benötigen Sie nie mehr Variablen als die Nummer Ihrer Spalte +1 .

So,

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

gibt dann die 3. Spalte aus. Wie in meinem Kommentar angegeben ...

Ein Pipeline-Lesevorgang wird in einer Umgebung ausgeführt, in der keine Variablen an das aufrufende Skript übergeben werden.

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'`


Die Array-Lösung

Wir erhalten dann die Antwort von @frayser, bei der die Shell-Variable IFS, die standardmäßig ein Leerzeichen enthält, verwendet wird, um die Zeichenfolge in ein Array aufzuteilen. Es funktioniert jedoch nur in Bash. Dash und Ash unterstützen es nicht. Es fiel mir wirklich schwer, einen String in einer Busybox-Sache in Komponenten aufzuteilen. Es ist einfach genug, eine einzelne Komponente zu erhalten (z. B. mit awk) und diese dann für jeden benötigten Parameter zu wiederholen. Aber dann rufen Sie wiederholt awk in derselben Zeile auf oder verwenden wiederholt einen Leseblock mit Echo in derselben Zeile. Welches ist nicht effizient oder hübsch. So spalten Sie am Ende mit ${name%% *}und so weiter. Sie sehnen sich nach Python-Kenntnissen, da Shell-Scripting nicht mehr viel Spaß macht, wenn die Hälfte oder mehr der Funktionen, an die Sie gewöhnt sind, nicht mehr vorhanden sind. Aber Sie können davon ausgehen, dass nicht einmal Python auf einem solchen System installiert wäre, und das war es nicht ;-).

Xennex81
quelle
Sie sollten in echo "$a"und echo "$c"obwohl Anführungszeichen um die Variable verwenden .
Tripleee
Es scheint jedoch so, als ob jeder Piped-Block in einer eigenen Subshell oder einem eigenen Prozess ausgeführt wird und Sie keine Variablen an den umschließenden Block zurückgeben können. Sie können die Ausgabe jedoch nach dem Echo erhalten. var=$(....... | { read a b c d; echo $c; }). Das funktioniert nur für eine einzelne (Zeichenfolge), obwohl Sie es in Bash mitar=($var)
Xennex81
@tripleee Ich denke nicht, dass dies in einer solchen Phase des Prozesses ein Problem ist. Sie werden früh genug feststellen, ob Sie das brauchen oder nicht, und wenn das irgendwann kaputt geht, ist es eine Lernstunde. Und dann weißt du, warum du diese doppelten Anführungszeichen verwenden musstest ;-). Und dann ist es nicht mehr etwas, was Sie von anderen gehört haben. Spiel mit Feuer! : D. : p.
Xennex81
ausgearbeitete Antwort: D
ncomputers
Dies war eine zu hilfreiche Antwort, als dass ich sie hätte sagen können.
Ivan X
4

Versuchen

ps |&
while read -p first second third fourth etc ; do
   if [[ $first == '11383' ]]
   then
       echo got: $fourth
   fi       
done
James Anderson
quelle
1
@flybywire - möglicherweise übertrieben für dieses einfache Beispiel, aber diese Redewendung ist hervorragend, wenn Sie die ausgewählten Daten komplexer verarbeiten müssen.
James Anderson
Beachten Sie auch, dass heutzutage die Standard-Scripting-Shell normalerweise nicht Bash ist.
David gegeben
2

Array-Variablen verwenden

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

oder

A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}
Frayser
quelle
2

Ähnlich wie bei der awk-Lösung von brianegge ist hier das Perl-Äquivalent:

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

-aAktiviert den Autosplit-Modus, der das @FArray mit den Spaltendaten füllt .
Verwenden -F,Sie diese Option, wenn Ihre Daten durch Kommas und nicht durch Leerzeichen getrennt sind.

Feld 3 wird gedruckt, da Perl von 0 statt 1 zu zählen beginnt

Chris Koknat
quelle
1
Vielen Dank für Ihre Perl-Lösung - ich wusste nichts über Autosplit und denke immer noch, dass Perl das Tool ist, um die anderen Tools zu beenden ..;).
Gerard ONeill
1

Das Erhalten der richtigen Zeile (Beispiel für Zeile Nr. 6) erfolgt mit Kopf und Schwanz, und das richtige Wort (Wort Nr. 4) kann mit awk erfasst werden:

command|head -n 6|tail -n 1|awk '{print $4}'
Soulmerge
quelle
Nur für zukünftige Leser zu beachten, dass awk auch nach Zeilen auswählen kann: awk NR=6 {print $4}wäre etwas effizienter
David Z
1
und damit meinte ich natürlich awk NR==6 {print $4}* doh *
David Z
0

Anstatt all diese Greps und Sachen zu machen, würde ich Ihnen raten, ps-Funktionen zum Ändern des Ausgabeformats zu verwenden.

ps -o cmd= -p 12345

Sie erhalten die cmmand-Zeile eines Prozesses mit der angegebenen PID und sonst nichts.

Dies ist POSIX-konform und kann daher als tragbar angesehen werden.

P Shved
quelle
1
flybywire gibt an, dass er nur ps als Beispiel verwendet, die Frage ist allgemeiner.
Oger Psalm33
0

Dein Befehl

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

verpasst ein tr -s, um Leerzeichen zu drücken, wie Entspannen in seiner Antwort erklärt .

Möglicherweise möchten Sie jedoch Folgendes verwenden awk, da alle diese Aktionen in einem einzigen Befehl ausgeführt werden:

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

Dies druckt die 4. Spalte in den Zeilen, die enthalten 11383. Wenn Sie möchten, dass dies übereinstimmt, 11383wenn es am Anfang der Zeile erscheint, können Sie sagen ps | awk '/^11383/ {print $4}'.

fedorqui 'SO hör auf zu schaden'
quelle
0

Bash's setanalysiert alle Ausgaben in Positionsparameter.

Mit dem set $(free -h)Befehl echo $7wird beispielsweise "Mem:" angezeigt.

dman
quelle
Diese Methode ist nur nützlich, wenn der Befehl eine einzige Ausgabezeile hat. Nicht generisch genug.
Codeforester
Das ist nicht wahr, alle Ausgaben werden unabhängig von den Zeilen in Positionsparametern platziert. ex set $(sar -r 1 1); echo "${23}"
Dman
Mein Punkt war, dass es schwierig ist, die Position des Arguments zu bestimmen, wenn die Ausgabe umfangreich ist und viele Felder enthält. awkist der beste Weg, um es zu tun.
Codeforester
Dies ist nur eine andere Lösung. Das OP möchte möglicherweise keine awk-Sprache für diesen einzelnen Anwendungsfall lernen. Die Tags geben an bashund nicht awk.
Dman