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.


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

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

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ł ;-).


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

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]}

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


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

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}'.


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.


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:”


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.