Разделить вывод команды по столбцам, используя Bash?

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

  • запустите команду
  • захватить вывод
  • выберите строку
  • выберите столбец этой строки

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

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

Обратите внимание, что параметр 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

выведет третий столбец. Как указано в моем комментарии...

Чтение по каналам будет выполняться в среде, которая не передает переменные вызывающему script.

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, потому что на самом деле shell-скриптинг - это не большая забава, если половина или более функций, к которым вы привыкли, исчезли. Но вы можете предположить, что даже такой питон не будет установлен на такой системе, и это не было; -).

Ответ 4

попробовать

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

Ответ 5

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

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

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

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

Ответ 6

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

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

Ответ 7

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

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

или

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

Ответ 8

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

ps -o cmd= -p 12345

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

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

Ответ 9

Ваша команда

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

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

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

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

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

Ответ 10

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

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