Розділити вихід команди на стовпці за допомогою Bash?


87

Я хочу зробити це:

  1. запустити команду
  2. захоплення вихідних даних
  3. виберіть рядок
  4. виберіть стовпець цього рядка

Як приклад, скажімо, я хочу отримати ім'я команди з $PID(зверніть увагу, це лише приклад, я не припускаю, що це найпростіший спосіб отримати ім'я команди з ідентифікатора процесу - моя справжня проблема полягає в інша команда, вихідний формат якої я не можу контролювати).

Якщо я біжу, psя отримую:


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

Тепер я роблю ps | egrep 11383і отримую

11383 pts/1    00:00:00 bash

Наступний крок: ps | egrep 11383 | cut -d" " -f 4. Вихід:

<absolutely nothing/>

Проблема полягає в тому, що результат cutскорочується окремими пробілами, і, psдодаючи пробіли між 2-м і 3-м стовпцями, щоб зберегти певну схожість таблиці, cutвибирає порожній рядок. Звичайно, я міг cutби обрати 7-е, а не 4-те поле, але як я можу це знати, особливо коли вихідний результат заздалегідь змінний і невідомий.


2
Використовуйте awk (і ще 25 символів).
Michael Foukarakis

Відповіді:


178

Один простий спосіб - додати прохід, trщоб видавити будь-які повторювані роздільники полів:

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

1
Мені це подобається, схоже, trвін awk
легший

3
Я, як правило, погоджуюсь, але це може бути і тому, що я не навчився awk. :)
розслабтесь

Не буде працювати, якщо у вас трапляється процес з PID, який містить PID, який вас цікавить, як підрядок.
Девід Грейсон,

1
А також, нульові нульові поля будуть вимкнені, якщо деякі PID: s пробілені ліворуч, а інші ні.
триплі

68

Я думаю, що найпростіший спосіб - використовувати awk . Приклад:

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

4
Для сумісності з вихідним запитанням ps | awk "\$1==$PID{print\$4}"або (краще) ps | awk -v"PID=$PID" '$1=PID{print$4}'. Звичайно, на Linux ви могли просто зробити xargs -0n1 </proc/$PID/cmdline | head -n1або readlink /proc/$PID/exe, але так чи інакше ...
ефемієнт

Чи є ;в { print $4; }необхідності? Видалення, здається, для мене не впливає на Linux, просто цікаво, що це за ціль
igniteflow

@igniteflow, чи не вказує це на закінчення команди, якщо ви хочете продовжувати додавати минулі оператори print?
joshmcode

16

Зверніть увагу, що ця tr -s ' 'опція не видалить жодного пробілу. Якщо стовпець вирівняний по правому краю (як у pspid) ...

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

Тоді вирізання призведе до порожнього рядка для деяких з цих полів, якщо це перший стовпець:

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

19645
19731

Очевидно, якщо ви перед цим не ставите пробіл

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

Тепер для цього конкретного випадку номерів pid (а не імен) існує функція, що називається pgrep:

$ pgrep ssh


Функціонує оболонка

Однак загалом все-таки можливо використовувати функції оболонки стисло, оскільки в команді є акуратна річ read:

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

Перший параметр для читання, aвибирає перший стовпець, а якщо їх більше, все інше буде введено b. Як результат, вам ніколи не потрібно більше змінних, ніж номер вашого стовпця +1 .

Тому,

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

потім виведе 3-й стовпець. Як зазначено в моєму коментарі ...

Зчитування в трубопроводі буде виконано в середовищі, яке не передає змінні викличному сценарію.

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


Рішення масиву

Тоді ми закінчуємо відповіддю @frayser, яка полягає у використанні змінної оболонки IFS, яка за замовчуванням має пробіл, щоб розділити рядок на масив. Це працює лише в Bash. Тире та Еш його не підтримують. Мені було дуже важко розбити рядок на компоненти в Busybox. Досить просто отримати один компонент (наприклад, за допомогою awk), а потім повторити це для кожного потрібного параметра. Але тоді ви в кінцевому підсумку неодноразово викликаєте awk на одному рядку або неодноразово використовуєте блок читання з відлунням на тій самій лінії. Що неефективно чи красиво. Таким чином, ви в кінцевому підсумку розділяєте використання ${name%% *}і так далі. Змушує вас тужити за деякими навичками Python, адже насправді сценарії оболонки вже не дуже веселі, якщо половина або більше функцій, до яких ви звикли, зникли. Але ви можете припустити, що навіть python не був би встановлений у такій системі, і цього не було ;-).


Вам слід використовувати лапки навколо змінної in echo "$a"і echo "$c".
триплі

Здається, хоча, як ніби кожен трубопровідний блок виконується у своїй власній підшерепці або процесі, і ви не можете повернути будь-які змінні до блоку, що вкладає? Хоча ви можете отримати результат цього, повторивши його. var=$(....... | { read a b c d; echo $c; }). Це працює лише для одного (рядка), хоча в Bash ви можете розділити його на масив, використовуючиar=($var)
Xennex81,

@tripleee Я не думаю, що це проблема на такій стадії процесу. Незабаром ви дізнаєтесь, потрібно це вам чи ні, і якщо це в якийсь момент зламається, це навчальний урок. І тоді ви знаєте, чому вам довелося використовувати ці подвійні лапки ;-). І тоді це вже не те, що ви чули від інших. Грайте з вогнем! : D. : стор.
Xennex81,

розгорнута відповідь: D
ncomputers

Це була занадто корисна відповідь для мене, щоб я не говорив цього.
Іван Х

4

спробуй

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

1
@flybywire - можливо, надмірний для цього простого прикладу, але ця ідіома чудова, якщо вам потрібно зробити більш складну обробку вибраних даних.
Джеймс Андерсон,

Крім того, майте на увазі, що в наші дні скриптова скрипка за замовчуванням зазвичай не є bash.
Девід Дано

2

Використання змінних масиву

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

або

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

2

Подібно до рішення awk brianegge, ось еквівалент Perl:

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

-aвключає режим авторозділення, який заповнює @Fмасив даними стовпця.
Використовуйте, -F,якщо ваші дані розділені комами, а не пробілом.

Поле 3 друкується, оскільки Perl починає рахувати від 0, а не від 1


1
Дякуємо за рішення perl - не знали про автоспліт, і все ще думаєте, що perl - це інструмент для завершення роботи інших інструментів ..;).
Джерард ОН до

1

Отримання правильної лінії (наприклад, для рядка № 6) здійснюється за допомогою голови та хвоста, і правильне слово (слово № 4) можна захопити за допомогою awk:

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

Тільки зауважимо для майбутніх читачів, що awk може відібрати і за рядком: awk NR=6 {print $4}було б трохи ефективніше
David Z

1
і під цим, звичайно, я мав на увазі awk NR==6 {print $4}* doh *
Девід Z

1

Ваша команда

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

пропускає a tr -s вичавлювати простору, а розмотує пояснює в своїй відповіді .

Однак, можливо, ви хочете використовувати awk , оскільки він обробляє всі ці дії в одній команді:

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

Це друкує 4-й стовпець у тих рядках, що містять 11383. Якщо ви хочете, щоб це збігалося, 11383якщо воно з’являється на початку рядка, тоді можете сказати ps | awk '/^11383/ {print $4}'.


0

Замість того, щоб робити всі ці greps та інше, я б порадив вам використовувати ps можливості зміни вихідного формату.

ps -o cmd= -p 12345

Ви отримуєте рядок cmmand процесу із зазначеним pid і нічим іншим.

Це відповідає POSIX і, отже, може вважатися портативним.


1
flybywire стверджує, що він просто використовує ps як приклад, питання є більш загальним, ніж це.
Огре Псалом 33,

0

Bash setпроаналізує всі результати на параметри позиції.

Наприклад, за допомогою set $(free -h)команди, echo $7буде показано "Mem:"


Цей метод корисний лише тоді, коли команда має один рядок виводу. Недостатньо загальний.
codeforester

Це неправда, весь вивід розміщується в позиційних параметрах незалежно від рядків. колишній set $(sar -r 1 1); echo "${23}"
дман,

Моя думка полягала в тому, що важко визначити позицію аргументу, коли результат об’ємний і має багато полів. awkце найкращий спосіб це зробити.
codeforester

Це просто ще одне рішення. OP може не захотіти вивчати мову awk для цього випадку використання. Теги роблять стан, bashа ні awk.
дман
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.