Podziel wyjście polecenia na kolumny za pomocą Bash?

87

Chce to zrobić:

  1. uruchom polecenie
  2. uchwycić dane wyjściowe
  3. wybierz linię
  4. wybierz kolumnę tego wiersza

Jako przykład, powiedzmy, że chcę uzyskać nazwę polecenia z a $PID(proszę zauważyć, że to tylko przykład, nie sugeruję, że jest to najłatwiejszy sposób uzyskania nazwy polecenia z identyfikatora procesu - mój prawdziwy problem polega na inne polecenie, którego formatem wyjściowym nie mogę kontrolować).

Jeśli biegnę ps, dostaję:


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

Teraz robię ps | egrep 11383i dostaję

11383 pts/1    00:00:00 bash

Następny krok: ps | egrep 11383 | cut -d" " -f 4. Wynik to:

<absolutely nothing/>

Problem polega na tym, że cutodcina dane wyjściowe o pojedyncze spacje i psdodaje spacje między drugą i trzecią kolumną, aby zachować pewne podobieństwo do tabeli, cutwybiera pusty ciąg. Oczywiście mógłbym użyć cutdo wybrania siódmego, a nie czwartego pola, ale skąd mam wiedzieć, zwłaszcza gdy dane wyjściowe są zmienne i nieznane wcześniej.

Lecieć jak po sznurku
źródło
2
Użyj awk (i 25 dodatkowych znaków).
Michael Foukarakis

Odpowiedzi:

178

Jednym prostym sposobem jest dodanie przebiegu w trcelu wyciśnięcia wszelkich powtarzających się separatorów pól:

$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4
rozwijać
źródło
1
Podoba mi się ten, wygląda na to, że trjest lżejszy niżawk
flybywire
3
Zwykle się zgadzam, ale może to być również spowodowane tym, że nie nauczyłem się awk. :)
zrelaksuj się
Nie zadziała, jeśli masz proces z PID, który zawiera PID, który Cię interesuje jako podrzędną.
David Grayson
1
A także, numerki polowe będą wyłączone, jeśli niektóre PID są wypełnione po lewej stronie, a inne nie.
tripleee
68

Myślę, że najprostszym sposobem jest użycie awk . Przykład:

$ echo "11383 pts/1    00:00:00 bash" | awk '{ print $4; }'
bash
brianegge
źródło
4
Aby uzyskać zgodność z pierwotnym pytaniem ps | awk "\$1==$PID{print\$4}"lub (lepiej) ps | awk -v"PID=$PID" '$1=PID{print$4}'. Oczywiście na Linuksie możesz po prostu zrobić xargs -0n1 </proc/$PID/cmdline | head -n1lub readlink /proc/$PID/exe, ale w każdym razie ...
ephemient
Czy ;w { print $4; }wymagane? Usunięcie tego wydaje się nie mieć na mnie żadnego wpływu na Linuksie, jestem po prostu ciekawy jego celu
igniteflow
@igniteflow czy nie oznaczałoby to końca polecenia, gdybyś chciał kontynuować dodawanie poza instrukcją print?
joshmcode
16

Należy pamiętać, że tr -s ' 'opcja nie usunie żadnych pojedynczych spacji wiodących. Jeśli twoja kolumna jest wyrównana do prawej (jak w przypadku pspid) ...

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

Wycinanie spowoduje powstanie pustej linii dla niektórych z tych pól, jeśli jest to pierwsza kolumna:

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

19645
19731

Chyba że poprzedzisz go spacją, oczywiście

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

Teraz, dla tego konkretnego przypadku numerów pid (nie nazw), istnieje funkcja o nazwie pgrep:

$ pgrep ssh


Funkcje powłoki

Jednak ogólnie rzecz biorąc, nadal można używać funkcji powłoki w zwięzły sposób, ponieważ polecenie jest fajne read:

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

Pierwszy parametr do odczytania a,, wybiera pierwszą kolumnę, a jeśli jest ich więcej, wszystko inne zostanie wstawione b. W rezultacie nigdy nie potrzebujesz więcej zmiennych niż liczba Twojej kolumny +1 .

Więc,

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

wyświetli trzecią kolumnę. Jak wskazałem w moim komentarzu ...

Odczyt potokowy zostanie wykonany w środowisku, które nie przekazuje zmiennych do skryptu wywołującego.

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


Rozwiązanie macierzy

W rezultacie otrzymujemy odpowiedź od @frayser, która polega na użyciu zmiennej powłoki IFS, która domyślnie jest spacją, aby podzielić ciąg na tablicę. Działa jednak tylko w Bash. Dash i Ash tego nie obsługują. Naprawdę ciężko mi było rozdzielić łańcuch na komponenty w Busybox. Dość łatwo jest uzyskać pojedynczy komponent (np. Używając awk), a następnie powtórzyć to dla każdego potrzebnego parametru. Ale w końcu wielokrotnie wywołujesz awk w tej samej linii lub wielokrotnie używasz bloku odczytu z echem w tej samej linii. Co nie jest wydajne ani ładne. Więc kończysz rozdzielając używając ${name%% *}i tak dalej. Sprawia, że ​​tęsknisz za niektórymi umiejętnościami Pythona, ponieważ w rzeczywistości skrypty powłoki nie są już zbyt zabawne, jeśli zniknie połowa lub więcej funkcji, do których jesteś przyzwyczajony. Ale można założyć, że nawet python nie zostałby zainstalowany na takim systemie, a nie był ;-).

Xennex81
źródło
Powinieneś używać cudzysłowów wokół zmiennej w echo "$a"i echo "$c"chociaż.
tripleee
Wydaje się jednak, że każdy blok potokowy jest wykonywany we własnej podpowłoce lub procesie i nie można zwrócić żadnych zmiennych do otaczającego bloku? Chociaż możesz uzyskać wynik tego po powtórzeniu go. var=$(....... | { read a b c d; echo $c; }). To działa tylko dla pojedynczego (ciągu), chociaż w Bash możesz podzielić go na tablicę za pomocąar=($var)
Xennex81
@tripleee Nie sądzę, żeby to był problem na takim etapie procesu. Wkrótce przekonasz się, czy tego potrzebujesz, czy nie, a jeśli to się w którymś momencie zepsuje, będzie to lekcja nauki. A potem wiesz, dlaczego musiałeś użyć tych podwójnych cudzysłowów ;-). A potem nie jest to już coś, co słyszałeś od innych. Igrać z ogniem! :RE. : p.
Xennex81
rozwinięta odpowiedź: D
ncomputers
To była zbyt pomocna odpowiedź, bym tego nie powiedział.
Ivan X
4

próbować

ps |&
while read -p first second third fourth etc ; do
   if [[ $first == '11383' ]]
   then
       echo got: $fourth
   fi       
done
James Anderson
źródło
1
@flybywire - prawdopodobnie przesada dla tego prostego przykładu, ale ten idiom jest doskonały, jeśli chcesz wykonać bardziej złożone przetwarzanie wybranych danych.
James Anderson,
Należy również pamiętać, że obecnie domyślną powłoką skryptów zwykle nie jest bash.
David Given
2

Używanie zmiennych tablicowych

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

lub

A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}
frayser
źródło
2

Podobnie jak w rozwiązaniu awk brianegge, tutaj jest odpowiednik Perla:

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

-awłącza tryb autosplit, który zapełnia @Ftablicę danymi z kolumny.
Posługiwać się-F, jeśli dane są rozdzielane przecinkami, a nie spacjami.

Pole 3 jest drukowane, ponieważ Perl zaczyna liczyć od 0, a nie 1

Chris Koknat
źródło
1
Dziękuję za rozwiązanie w Perlu - nie wiedziałem o autosplicie i nadal uważam, że perl jest narzędziem do zakończenia innych narzędzi ..;).
Gerard ONeill
1

Uzyskanie poprawnej linii (na przykład dla linii nr 6) odbywa się za pomocą głowy i ogona, a prawidłowe słowo (słowo nr 4) można przechwycić za pomocą awk:

command|head -n 6|tail -n 1|awk '{print $4}'
soulmerge
źródło
Uwaga dla przyszłych czytelników, że awk może również wybierać wierszami: awk NR=6 {print $4}byłoby trochę bardziej wydajnie
David Z
1
i przez to oczywiście miałem na myśli awk NR==6 {print $4}* doh *
David Z
1

Twoja komenda

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

pomija a, tr -saby ścisnąć spacje, jak wyjaśnia relax w swojej odpowiedzi .

Możesz jednak chcieć użyć awk, ponieważ obsługuje wszystkie te akcje w jednym poleceniu:

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

Spowoduje to wypisanie czwartej kolumny w wierszach zawierających 11383. Jeśli chcesz, aby to pasowało, 11383jeśli pojawia się na początku wiersza, możesz powiedzieć ps | awk '/^11383/ {print $4}'.

fedorqui 'SO przestań szkodzić'
źródło
0

Zamiast robić te wszystkie grepsy i inne rzeczy, radziłbym użyć możliwości ps zmiany formatu wyjściowego.

ps -o cmd= -p 12345

Otrzymasz wiersz cmmand procesu z określonym pid i nic więcej.

Jest to zgodne z POSIX i dlatego może być uważane za przenośne.

P Shved
źródło
1
flybywire stwierdza, że ​​używa ps jako przykładu, pytanie jest bardziej ogólne.
Ogre Psalm 33
0

Bash setprzetworzy wszystkie dane wyjściowe na parametry pozycji.

Na przykład set $(free -h)polecenie echo $7spowoduje wyświetlenie „Mem:”

dman
źródło
Ta metoda jest przydatna tylko wtedy, gdy polecenie ma jeden wiersz danych wyjściowych. Nie dość ogólne.
codeforester
To nieprawda, wszystkie dane wyjściowe są umieszczane w parametrach pozycyjnych, niezależnie od wierszy. ex set $(sar -r 1 1); echo "${23}"
dman
Chodziło mi o to, że trudno jest określić pozycję argumentu, gdy wynik jest obszerny i ma wiele pól. awkto najlepszy sposób, aby się do tego zabrać.
codeforester
To tylko kolejne rozwiązanie. OP może nie chcieć uczyć się języka awk dla tego pojedynczego przypadku użycia. Tagi określają, basha nie awk.
dman