Bash: динамически перенаправлять стандартный ввод в скрипт

Я пытался сделать это, чтобы решить, стоит ли перенаправить stdin в файл или нет:

[ ...some condition here... ] && input=$fileName || input="&0"
./myScript < $input

но это не работает, потому что, когда переменная $input "&0", bash интерпретирует ее как имя файла.

однако я мог бы просто сделать:

if [ ...condition... ];then
    ./myScript <$fileName
else
    ./myScript

проблема в том, что ./myScript - это длинная командная строка, которую я не хочу дублировать, и я не хочу создавать для нее функцию, потому что она тоже не такая длинная (это того не стоит).

затем мне пришло в голову сделать вот что:--4-->

[ ...condition... ] && input=$fileName || input=  #empty
cat $input | ./myScript

но для этого требуется выполнить еще одну команду и канал (т. е. подсеть).
Есть ли другой способ, который проще и эффективнее?

7 ответов


прежде всего stdin - это файловый дескриптор 0 (ноль), а не 1 (который является stdout).

вы можете дублировать дескрипторы файлов или использовать имена файлов условно следующим образом:

[[ some_condition ]] && exec 3<"$filename" || exec 3<&0

some_long_command_line <&3

обратите внимание, что команда будет выполняться второй exec Если либо условие false или первый exec не удается. Если вы не хотите, чтобы потенциальный сбой сделал это, вы должны использовать if / else:

if [[ some_condition ]]
then
    exec 3<"$filename"
else
    exec 3<&0
fi

но затем последующие перенаправление из файлового дескриптора 3 завершится неудачно, если первое перенаправление завершится неудачно (после выполнения условия true).


(
    if [ ...some condition here... ]; then
        exec <$fileName
    fi
    exec ./myscript
)

в подрешетке условно перенаправьте stdin и выполните скрипт.


стандартный ввод также может быть представлен специальным файлом устройства /dev/stdin, Так что использование в качестве имени файла будет работать.

file="/dev/stdin"
./myscript < "$file"

как о

function runfrom {
    local input=""
    shift
    case "$input" in
        -) "$@" ;;
        *) "$@" < "$input" ;;
    esac
}

я использовал знак минус для обозначения стандартного ввода, потому что это традиционно для многих программ Unix.

Теперь вы пишите

[ ... condition ... ] && input="$fileName" || input="-"
runfrom "$input" my-complicated-command with many arguments

Я нахожу эти функции / команды, которые принимают команды в качестве аргументов (например,xargs(1)) может быть очень полезным, и они хорошо сочиняют.


если вы будете осторожны, вы можете использовать' - Это, наверное, не стоит; это вряд ли будет узким местом.

еще лучше дизайн myScript так что он работает как обычный фильтр Unix-он читает со стандартного ввода, если ему не дается один или несколько файлов для работы (например, cat или grep в качестве примеров). Этот дизайн основан на долгом и здравом опыте - и поэтому стоит подражать, чтобы избежать необходимости иметь дело с такими проблемами, как это.


использовать eval:

#! /bin/bash

[ $# -gt 0 ] && input="'""'" || input="&1"

eval "./myScript <$input"

это простой дублер для myScript

#! /usr/bin/perl -lp
$_ = reverse

производит следующий вывод:

$ ./myDemux myScript
pl- lrep/nib/rsu/ !#
esrever = _$

$ ./myDemux
foo
oof
bar
rab
baz
zab

обратите внимание, что он также обрабатывает пробелы во входах:

$ ./myDemux foo\ bar
eman eht ni ecaps a htiw elif

для ввода в трубу до myScript используйте подмена процесса:

$ ./myDemux <(md5sum /etc/issue)
eussi/cte/  01672098e5a1807213d5ba16e00a7ad0

обратите внимание, что если вы пытаетесь передать вывод напрямую, как в

$ md5sum /etc/issue | ./myDemux

он будет висеть, ожидая ввода от терминал, тогда как ephemient это!--12--> не имеет этого недостатка.

небольшое изменение производит желаемое поведение:

#! /bin/bash

[ $# -gt 0 ] && input="'""'" || input=/dev/stdin
eval "./myScript <$input"

люди показывают вам очень длинные сценарии, но.... вы получаете bash trap :) Вы должны привести все в bash. например, вам нужен файл списка с именем &0 .

filename= '&0 ' #справа Общ $имя_файла #неправильно! это заменяет $filename и интерпретирует &0 ls "$filename " #right

другой, файлы с пробелами.

filename= ' некоторый файл с пробелами ' LS $filename #неправильно, bash вырезает первое и последнее пространство и уменьшает несколько пробелов между словами with и пробелами лат "$filename " righ

то же самое в вашем сценарии. пожалуйста, измените:

./myScript < $input

to

./myScript < "$input"

все. у Баша больше ловушек. Я предлагаю сделать цитату для "$file " по той же причине. пробелы и другие символы, которые могут быть интерпретированы, всегда создают проблемы.

но как насчет /dev / stdin ? это можно использовать только тогда, когда вы перенаправили stdin и хотите напечатать что-то в реальном stdin.

Итак, ваш скрипт должен показать, как это:

[ ...some condition here... ] && input="$fileName" || input="&0"
./myScript < "$input"