bash: разделить вывод команды по столбцам - PullRequest
74 голосов
/ 27 октября 2009

Я хочу сделать это:

  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-е поле, но как я могу знать, особенно, когда выходные данные являются переменными и заранее неизвестны.

Ответы [ 10 ]

148 голосов
/ 27 октября 2009

Один простой способ - добавить проход <a href="http://linux.die.net/man/1/tr" rel="noreferrer">tr</a>, чтобы выжать из повторяющихся разделителей полей:

$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4
61 голосов
/ 27 октября 2009

Я думаю, что самый простой способ - использовать awk . Пример:

$ echo "11383 pts/1    00:00:00 bash" | awk '{ print $4; }'
bash
8 голосов
/ 16 февраля 2015

Обратите внимание, что опция tr -s ' ' не удалит ни одного начального пробела. Если ваш столбец выровнен по правому краю (как при ps pid) ...

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


The Array Solution

Таким образом, мы в итоге получаем ответ от @frayser, который должен использовать переменную оболочки IFS, которая по умолчанию устанавливается на пробел, чтобы разбить строку на массив. Это работает только в Bash, хотя. Дэш и Эш не поддерживают это. Мне было очень трудно разделить строку на компоненты в Busybox. Достаточно просто получить один компонент (например, используя awk), а затем повторить это для каждого необходимого параметра. Но затем вы в конечном итоге неоднократно вызываете awk в одной и той же строке или постоянно используете блок чтения с echo в той же строке. Что не эффективно или красиво. Таким образом, вы в конечном итоге разбиваете, используя ${name%% *} и так далее. Заставляет вас жаждать некоторых навыков Python, потому что на самом деле создание сценариев оболочки уже не доставляет большого удовольствия, если половина или более функций, к которым вы привыкли, пропали. Но вы можете предположить, что даже python не будет установлен в такой системе, и это не так; -).

3 голосов
/ 27 октября 2009

1001 * попробовать *

ps |&
while read -p first second third fourth etc ; do
   if [[ $first == '11383' ]]
   then
       echo got: $fourth
   fi       
done
2 голосов
/ 11 сентября 2015

Подобно awk-решению brianegge, вот эквивалент Perl:

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

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

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

1 голос
/ 13 декабря 2012

Использование переменных массива

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

или

A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}
1 голос
/ 27 октября 2009

Получение правильной строки (пример для строки № 6) выполняется с помощью головы и хвоста, а правильное слово (слово № 4) может быть записано с помощью awk:

command|head -n 6|tail -n 1|awk '{print $4}'
0 голосов
/ 29 мая 2017

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

Например, с командой set $(free -h), echo $7 покажет «Mem:»

0 голосов
/ 22 мая 2015

Ваша команда

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

пропускает tr -s, чтобы сжать пробелы, как раскрутка объясняет в его ответ .

Однако, возможно, вы захотите использовать awk, поскольку он обрабатывает все эти действия в одной команде:

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

Это печатает 4-й столбец в тех строках, которые содержат 11383. Если вы хотите, чтобы это совпадало с 11383, если оно появляется в начале строки, тогда вы можете сказать ps | awk '/^11383/ {print $4}'.

0 голосов
/ 27 октября 2009

Вместо того, чтобы делать все эти greps и прочее, я бы посоветовал вам использовать возможности ps для изменения формата вывода.

ps -o cmd= -p 12345

Вы получаете строку cmmand процесса с указанным pid и ничего больше.

Это POSIX-совместимый и, следовательно, может считаться переносимым.

...