Алмазный оператор Perl: это можно сделать в bash? - PullRequest
4 голосов
/ 09 апреля 2009

Есть ли идиоматический способ симуляции алмазного оператора Perl в bash? С оператором алмазов

script.sh | ...

читает ввод для ввода и

script.sh file1 file2 | ...

читает file1 и file2 для ввода.

Еще одно ограничение заключается в том, что я хочу использовать stdin в script.sh для чего-то другого, кроме ввода в мой собственный скрипт. Приведенный ниже код делает то, что я хочу, для случая file1 file2 ... выше, но не для данных, предоставляемых на stdin.

command - $@ <<EOF
some_code_for_first_argument_of_command_here
EOF

Я бы предпочел решение Bash, но любая оболочка Unix в порядке.

Редактировать: для пояснения, вот содержание script.sh:

#!/bin/bash
command - $@ <<EOF
some_code_for_first_argument_of_command_here
EOF

Я хочу, чтобы это работало так же, как оператор алмаза работал бы в Perl, но сейчас он обрабатывает только имена файлов как аргументы.

Редактировать 2: я не могу ничего сделать, что идет

cat XXX | command

потому что стандартный ввод команды не является данными пользователя . Стандартом для команды являются мои данные в Here-Doc. Я хотел бы, чтобы пользовательские данные входили в stdin моего скрипта, но это не может быть stdin вызова команды внутри моего скрипта.

Ответы [ 7 ]

6 голосов
/ 09 апреля 2009

Конечно, это вполне выполнимо:

#!/bin/bash
cat $@ | some_command_goes_here

Пользователи могут затем вызывать ваш сценарий без аргументов (или '-') для чтения из стандартного ввода или из нескольких файлов, каждый из которых будет прочитан.

Если вы хотите обработать содержимое этих файлов (скажем, построчно), вы можете сделать что-то вроде этого:

for line in $(cat $@); do
    echo "I read: $line"
done

Edit: изменен $* на $@ для обработки пробелов в именах файлов, благодаря полезному комментарию.

4 голосов
/ 09 апреля 2009

Вид сыра, но как насчет

cat file1 file2 | script.sh
2 голосов
/ 11 апреля 2009

Я (как и все остальные, похоже) немного запутался в том, какая именно цель здесь, поэтому я дам три возможных ответа, которые могут охватить то, что вы действительно хотите. Во-первых, относительно простая цель - заставить скрипт читать из списка файлов (предоставленных в командной строке) или из стандартного stdin:

if [ $# -gt 0 ]; then
  exec < <(cat "$@")
fi
# From this point on, the script's stdin is redirected from the files
# (if any) supplied on the command line

Примечание. Использование $ @ в двойных кавычках - лучший способ избежать проблем с забавными символами (например, пробелами) в именах файлов - $ * и без кавычек $ @ оба приводят в замешательство. Используемый здесь трюк <() предназначен только для bash; он запускает cat в фоновом режиме для подачи данных из файлов, предоставленных в командной строке, а затем мы используем exec для замены stdin скрипта на вывод из cat. </p>

... но это, кажется, не то, что вы на самом деле хотите. То, что вы действительно хотите - это передать предоставленные имена файлов или stdin скрипта в качестве аргументов команде внутри скрипта. Это требует своего рода обратного процесса: преобразование стандартного сценария в файл (фактически именованный канал), имя которого можно передать команде. Как это:

if [[ $# -gt 0 ]]; then
  command "$@" <<EOF
here-doc goes here
EOF
else
  command <(cat) <<EOF
here-doc goes here
EOF
fi

Используется <() для запуска stdin скрипта через cat в именованный канал, который затем передается команде в качестве аргумента. Между тем, командный стандарт взят из Here-Doc. </p>

Теперь, я думаю, это то, что вы хотите сделать, но это не совсем то, что вы просили, то есть оба перенаправить ввод скрипта из предоставленных файлов и передать stdin команде внутри скрипта. Это может быть сделано путем объединения вышеперечисленных методов:

if [ $# -gt 0 ]; then
  exec < <(cat "$@")
fi

command <(cat) <<EOF
here-doc goes here
EOF

... хотя я не могу понять, почему вы действительно хотите это сделать.

1 голос
/ 10 апреля 2009

Вы хотите взять первый аргумент и что-то с ним сделать, а затем либо читать из любых указанных файлов, либо использовать stdin, если файлов нет?

Лично я бы предложил использовать getopt для указания аргументов, используя синтаксис "-a значение", чтобы помочь устранить неоднозначность, но это только я. Вот как я это сделаю в bash без getopts:

firstarg=${1?:usage: $0 arg [file1 .. fileN]}
shift
typeset -a files
if [[ ${#@} -gt 0 ]]
then
  files=( "$@" )
else
  files=( "/dev/stdin" )
fi
for file in "${files[@]}"
do
  whatever_you_want < "$file"
done

Оператор?: Умрет, если аргументы не указаны, так как вам, по-видимому, нужен хотя бы один аргумент в любом случае. После этого переместите аргументы на единицу, а затем либо используйте оставшиеся аргументы в качестве списка файлов, либо специальный файловый дескриптор bash "/ dev / stdin", если других аргументов не было.

Я думаю, что "если файлы не указаны, используйте / dev / stdin - в противном случае используйте файлы в командной строке", это, вероятно, то, что вы ищете, но остальная часть кода по крайней мере полезна для контекст.

1 голос
/ 09 апреля 2009

Оператор Perl diamond по существу перебирает все аргументы командной строки, рассматривая каждый из них как имя файла. Он открывает каждый файл и читает их построчно. Вот некоторый код bash, который будет делать примерно то же самое.

for f in "$@"
do
   # Do something with $f, such as...
   cat $f | command1 | command2
   -or-
   command1 < $f
   -or-
   # Read $f line-by-line
   cat $f | while read line_from_f
   do
      # Do stuff with $line_from_f
   done
done
0 голосов
/ 09 апреля 2009

Почему бы не использовать `` cat @ * в сценарии? Например:

x=`cat $*`
echo $x
0 голосов
/ 09 апреля 2009

Также немного сыровато, но как насчет этого:

if [[ $# -eq 0 ]]
then
  # read from stdin
else
  # read from $* (args)
fi

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

...