Можно ли получить доступ ко всей командной строке, включая каналы в скрипте bash?

8

Например, командная строка:

test.sh arg1 | grep "xyz"

Можно ли получить полную командную строку, включая следующий grep в bash-скрипте test.sh?

hellcode
источник
Вы можете уточнить, что вы подразумеваете под "командной строкой"?
Барт
Мне просто интересно, есть ли специальная переменная доллара, которая содержит полную строку (командную строку), а не только имя скрипта и его аргументы
адский код
2
Что бы вы использовали для этого?
Кусалананда
9
@ shellcode вам не нужно знать, если вы находитесь в трубе для этого. Просто проверьте, является ли вывод TTY. [ -t 1 ] unix.stackexchange.com/a/401938/70524
Муру
1
В связи с: unix.stackexchange.com/q/485271/117549
Джефф Шаллер

Ответы:

6

Там нет никакого способа сделать это в целом .

Но интерактивная bashоболочка может использовать механизм истории и DEBUGловушку, чтобы «сообщить» командам, которые она выполняет, полную командную строку, частью которой они являются, через переменную среды:

$ trap 'export LC=$(fc -nl -0); LC=${LC#? }' DEBUG
$ sh -c 'printf "last_command={%s}\n" "$LC"' | cat; true
last_command={sh -c 'printf "last_command={%s}\n" "$LC"' | cat; true}
mosvy
источник
13

нет

bash (или ваша оболочка) выполнит две разные команды.

  1. test.sh arg1
  2. grep "xyz"

test.sh не мог знать о следующем grep.

однако вы можете узнать, что находитесь «внутри» трубы, протестировав /proc/self/fd/1

test.sh

#!/bin/bash

file /proc/self/fd/1

который работает как

> ./test.sh
/proc/self/fd/1: symbolic link to /dev/pts/0
> ./test.sh | cat
/proc/self/fd/1: broken symbolic link to pipe:[25544239]

(Изменить) см . Комментарий Муру о том, что вы знаете, находитесь ли вы на трубе.

вам не нужно знать, если вы находитесь в трубе для этого. Просто проверьте, является ли вывод TTY. [ -t 1 ] https://unix.stackexchange.com/a/401938/70524

Archemar
источник
Хотя это полезно, это работает только в Linux, но не в других Unixes
Скотт Эрл
2

Используя /proc/self/fd, вы можете увидеть, находитесь ли вы в конвейере, а также идентификатор для канала. Если вы выполняете /proc/\*/fdпоиск подходящего канала, вы можете найти PID другого конца канала. С помощью PID вы можете затем прочитать /proc/$PID/cmdlineи повторить процесс в своих файловых дескрипторах, чтобы найти, к чему он подключен.

$ cat | cat | cat &
$ ps
  PID TTY          TIME CMD
 6942 pts/16   00:00:00 cat
 6943 pts/16   00:00:00 cat
 6944 pts/16   00:00:00 cat
 7201 pts/16   00:00:00 ps
20925 pts/16   00:00:00 bash
$ ls -l /proc/6942/fd
lrwx------. 1 tim tim 64 Jul 24 19:59 0 -> /dev/pts/16
l-wx------. 1 tim tim 64 Jul 24 19:59 1 -> 'pipe:[49581130]'
lrwx------. 1 tim tim 64 Jul 24 19:59 2 -> /dev/pts/16
$ ls -l /proc/6943/fd
lr-x------. 1 tim tim 64 Jul 24 19:59 0 -> 'pipe:[49581130]'
l-wx------. 1 tim tim 64 Jul 24 19:59 1 -> 'pipe:[49581132]'
lrwx------. 1 tim tim 64 Jul 24 19:59 2 -> /dev/pts/16
$ ls -l /proc/6944/fd
lr-x------. 1 tim tim 64 Jul 24 19:59 0 -> 'pipe:[49581132]'
lrwx------. 1 tim tim 64 Jul 24 19:59 1 -> /dev/pts/16
lrwx------. 1 tim tim 64 Jul 24 19:59 2 -> /dev/pts/16

Также, если вам повезет, различные команды в конвейере получат последовательные идентификаторы PID, что сделает его немного проще.

На самом деле у меня нет сценария для этого, но я доказал концепцию.

Тим Андерсон
источник
1

Другим способом может быть доступ к $BASH_COMMANDавтоматической переменной, но она по своей природе нестабильна и трудно уловить требуемое значение.

Я думаю, что вы можете поймать его только через an eval, который также включает специальный вызов ваших командных строк, например:

CMD="${BASH_COMMAND##* eval }" eval './test.sh arg1 | grep "xyz"'

Здесь $BASH_COMMANDрасширяется, а также очищается до evalстроки, и результирующая строка, таким образом, «снимается» в вспомогательную $CMDпеременную.

Маленький пример:

$ cat test.sh
#!/bin/sh

printf 'you are running %s\n' "$CMD"
sleep 1
echo bye bye
$
$ CMD="${BASH_COMMAND##* eval }" eval './test.sh | { grep -nH "."; }'
(standard input):1:you are running './test.sh | { grep -nH "."; }'
(standard input):2:bye bye
$

Естественно, он также может работать (на самом деле лучше) при вызове скриптов, например, sh -cили bash -c, как в:

$
$ CMD="${BASH_COMMAND}" sh -c './test.sh | { grep -nH "."; }'
(standard input):1:you are running CMD="${BASH_COMMAND}" sh -c './test.sh | { grep -nH "."; }'
(standard input):2:bye bye
$

Здесь без очистки переменной.

LL3
источник
1

Спасибо за ваши ответы. Я проверил разные вещи и пришел к следующему сценарию тестирования:

test.sh:

hist=`fc -nl -0`
# remove leading and trailing whitespaces
hist="$(echo "${hist}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
echo "Command line from history: '$hist'"

if [ -t 1 ]; then
  echo "Direct output to TTY, no pipe involved."
else
  echo "No TTY, maybe a piped command."
fi

if [ -p /dev/stdout ]; then
  echo "stdout is a pipe."
else
  echo "stdout is not a pipe."
fi

readlink -e /proc/self/fd/1
rst=$?
if [ $rst -eq 0 ]; then
  echo "Readlink test status okay, no pipe involved."
else
  echo "Readlink test status error $rst, maybe a piped command."
fi

тесты:

$ ./test.sh test1
Command line from history: './test.sh test1'
Direct output to TTY, no pipe involved.
stdout is not a pipe.
/dev/pts/3
Readlink test status okay, no pipe involved.

$ ./test.sh test2 | cat
Command line from history: './test.sh test2 | cat'
No TTY, maybe a piped command.
stdout is a pipe.
Readlink test status error 1, maybe a piped command.

$ echo "another command before pipe doesn't matter" | ./test.sh test3
Command line from history: 'echo "another command before pipe doesn't matter" | ./test.sh test3'
Direct output to TTY, no pipe involved.
stdout is not a pipe.
/dev/pts/3
Readlink test status okay, no pipe involved.

История командной строки работает только без Шебанга в верхней строке скрипта. Не знаю, будет ли это работать надежно и на других системах.

Я не смог подавить вывод из «readlink» (или «file», как предложено в Archemar), когда статус был успешным («/ dev / pts / 3»). Передача вывода в / dev / null или в переменную может привести к сбоям. Так что это не будет вариант для меня в сценарии.

Проверка TTY, о которой говорилось в muru, проста и, возможно, уже достаточна для некоторых случаев использования.

Редактировать: Моя заслуга в Mosvy, потому что вопрос был в том, как получить полную командную строку, а не просто определить, находится ли сценарий на конвейере. Мне нравится простая часть "fc -nl -0" в его ответе, потому что дальнейшая настройка системы не требуется. Это не 100-процентное решение, но это только для моего личного использования и, следовательно, достаточно. Спасибо всем остальным за вашу помощь.

hellcode
источник
Проверка TTY также может быть сделано для стандартного ввода: [ -t 0 ]. Таким образом, вы можете проверить, является ли stdin или stdout TTY и действовать соответствующим образом.
Муру
Если вы хотите узнать, является ли stdout каналом, в Linux вы можете использовать его if [ -p /dev/stdout ]; ...(точно так же, как readlink /proc/self/fd/..это не работает в BSD).
Мосви
2
Скрипт нуждается в работе ИМНШО. echo -eпочти наверняка не хочет -e. Вам нужно больше тестов, перенаправляющих в файл, вызываемых внутри $(...). Однако я призываю вас подумать, если это хорошая идея. Программы, lsкоторые меняют свой вывод в зависимости от того, выводят ли они на tty или канал, раздражают.
Икар