bash: разделить вывод команды по столбцам

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

  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 ответов


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

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

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

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

обратите внимание: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


функции оболочки

однако, в общем, на самом деле все еще можно использовать функции оболочки в краткой форме, потому что есть аккуратная вещь о :

$ <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, которая по умолчанию имеет пробел, чтобы разделить строку на массив. Это только работает в Баш. Дэш и Эш не поддерживают его. Мне было очень сложно разбить строку на компоненты в Busybox. Достаточно легко получить один компонент (например, с помощью awk), а затем повторить это для каждого нужного вам параметра. Но затем вы снова и снова вызываете awk в одной строке или повторно используете блок чтения с echo в одной строке. Что не эффективно и не красиво. Таким образом, вы в конечном итоге разделяете с помощью ${name%% *} и так далее. Заставляет вас тосковать по Питону навыки, потому что на самом деле оболочки сценариев не очень весело больше, если половина или более функций, к которым вы привыкли, ушли. Но вы можете предположить, что даже python не будет установлен в такой системе, и это не так ;-).


попробовать

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

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

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

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

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


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

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

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

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

или

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

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

ps -o cmd= -p 12345

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

Это POSIX-соответствует и, таким образом, может считаться портативным.


команда

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

мимо tr -s сжать пространства, как unwind объясняет в ответ.

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

ps | awk '/11383/ {print }'

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


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

например,, echo покажет "Mem:"