Разделить вывод команды по столбцам с помощью 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 символов).
— Майкл Фукаракис,

Ответы:


178

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

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

1
Мне нравится этот, похоже, trон легче, чемawk
— flybywire

3
Я был бы склонен согласиться, но это также может быть связано с тем, что я не изучал awk. :)
— раскрутите

Не будет работать, если у вас есть процесс с PID, который содержит интересующий вас PID в качестве подстроки.
— Дэвид Грейсон

1
Кроме того, объединители полей будут отключены, если некоторые PID: s заполнены пробелом слева, а другие - нет.
— Tripleee

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, но в любом случае ...
— ephemient

Требуется ли ;in { print $4; }? Удаление его, похоже, не повлияло на меня в Linux, мне просто любопытно, с какой целью
— igniteflow

@igniteflow не будет ли это обозначать конец команды, если вы хотите продолжить добавление после оператора печати?
— 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 не был бы установлен в такой системе, и это не так ;-).


Вы должны использовать кавычки вокруг переменной в echo "$a"и echo "$c".
— Tripleee

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

@tripleee Я не думаю, что это проблема на такой стадии процесса. Вы скоро поймете, нужно вам это или нет, и если в какой-то момент это сломается, это будет уроком. И тогда вы знаете, почему вам пришлось использовать эти двойные кавычки ;-). И тогда это уже не то, что вы слышали от других. Играй с огнем! : D. :п.
— Xennex81 04


Это был слишком полезный ответ, чтобы не говорить об этом.
— Иван X

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

Аналогично решению brianegge awk, вот его эквивалент на Perl:

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

-aвключает режим автоматического разделения, который заполняет @Fмассив данными столбца.
Используйте, -F,если ваши данные разделены запятыми, а не пробелами.

Поле 3 печатается, поскольку Perl начинает отсчет с 0, а не с 1.


1
Спасибо за ваше решение для Perl - не знал об авторазделении и все еще думаю, что Perl - это инструмент, чтобы покончить с другими инструментами ..;).
— Gerard ONeill

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}* дох *
— David Z

1

Ваша команда

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

скучает, 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 в качестве примера, вопрос более общий, чем этот.
— Ogre Psalm33

0

Bash setпроанализирует весь вывод в параметрах позиции.

Например, с set $(free -h)командой echo $7отобразится "Mem:"


Этот метод полезен только тогда, когда команда имеет единственную строку вывода. Не достаточно общий.
— codeforester

Это неверно, весь вывод помещается в позиционные параметры независимо от строк. ex set $(sar -r 1 1); echo "${23}"
— dman

Моя точка зрения заключалась в том, что трудно определить позицию аргумента, когда вывод объемный и имеет много полей. awkэто лучший способ сделать это.
— codeforester

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