Есть ли способ обойти канал в сценарии или функции оболочки?

Я хотел бы написать небольшую обертку вокруг ps, чтобы независимо от того, какие параметры или если я выполню grep результат, я мог бы получить первую строку вывода ps, который говорит мне, что это за столбцы. Например, вывод ps_wrapper -aux | grep thingбудет выглядеть примерно так:

USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
name       33925  1.0  0.0   9972  5528 pts/0    Ss   19:34   0:00 /bin/thing

Я попытался посмотреть, может ли teeбыть полезным, и попробовал что-то вроде

function ps_wrapper {
    tmpfile=$(mktemp)
    ps $@ > $tmpfile
    head -n 1 $tmpfile >> /dev/stdout
    cat $tmpfile
}

, но вскоре я понял это, конечно, даже если я написал " вручную" в /dev/stdout, он все равно будет передан в grep.

Я хочу напечатать первую строку фактической команды, которую я запускаю, потому что столбцы, напечатанные ps, могут меняться в зависимости от используемых параметров.

Я использую zsh, но я не возражаю против использования другого инструмента, если есть более подходящий.

Спасибо, что прочитали.

3
16.06.2020, 21:15
1 ответ

В общем случае невозможно «обойти канал», например, отправить вывод туда, куда бы он направлялся, если бы канала не было. Однако можно обойти канал в смысле перенаправления вывода в другое место по вашему выбору, например, на терминал. Специальный файл /dev/ttyвсегда представляет текущий терминал.

function ps_wrapper {
    tmpfile=$(mktemp)
    ps "$@" > $tmpfile
    head -n 1 $tmpfile >/dev/tty
    cat $tmpfile
    rm $tmpfile
}

Также возможно обойти конвейер, если вы «сохраните» исходное местоположение и передадите его через файловый дескриптор . Но вы не можете сделать это из функции ps_wrapper.

function ps_wrapper {
    tmpfile=$(mktemp)
    ps "$@" > $tmpfile
    head -n 1 $tmpfile >&3
    cat $tmpfile
    rm $tmpfile
}
{ ps_wrapper … | grep …; } 3>&1

Существует множество способов избежать создания временного файла. Я упомяну несколько. Если не указано иное, решения в этом ответе работают в bash, если вы добавите двойные кавычки вокруг подстановок переменных и команд .

Если вы хотите изменить способ вызова функции, вы можете вызывать headи grepпоследовательно с правой -стороны конвейера. headпрочитает и напечатает первую строку, а остальную часть оставит для последующего использования.

ps … | { head -n 1; grep …; }

Вы можете заменить процесс с помощью teeили zsh, встроенного -вtee-подобно функции (multios), чтобы дублировать вывод, отправляя один поток на head -n 1и другой команде по вашему выбору. Однако, если вы просто направляете каждый поток в команду, между ними будет гонка, и если headнедостаточно быстро, первая строка может не оказаться наверху. Это, вероятно, часто будет работать, потому что headдовольно быстро, но нет никакой гарантии, например, если grepнаходится в кеше диска, а headнет.

ps | tee >(head -n 1 >/dev/tty) | grep …
ps >(head -n 1 >/dev/tty) | grep …         # zsh only, only if multios is not disabled

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

function ps_wrapper {
  ps "$@" | awk 'NR == 1 {print >"/dev/tty"} NR != 1 {print}'
}

Пояснения:

  • awk обрабатывает ввод построчно.
  • УСЛОВИЕ { КОД } выполняет КОД в строках, которые удовлетворяют УСЛОВИЕ .
  • NR— номер строки.
  • Перенаправление в awk работает не совсем так, как в оболочке, но здесь достаточно похоже.
  • printбез аргумента выводит строку ввода.

Другим подходом было бы встроить функциональность фильтра в единую команду. Выберите строку, которая не будет отображаться в аргументах ps, чтобы использовать ее в качестве разделителя, например |.

function pipe_preserving_first_line {
  local lhs
  lhs=()
  while [[ $1 != '|' ]]; do
    lhs+=($1)
    shift
  done
  shift
  "${lhs[@]}" | {
    head -n 1;
    "$@"
  }
}
pipe_preserving_first_line ps u \| grep foo

Вместо использования grep вы можете использовать средства сопоставления ps, такие как -Cдля сопоставления процесса по его имени команды.

ps uww -C mycommand

Вместо использования grep вы можете использовать средства сопоставления pgrep.

ps -p $(pgrep -d, mycommand)
4
18.03.2021, 23:26

Теги

Похожие вопросы