Разделить вывод команды по столбцам с помощью 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.