Как написать скрипт, который принимает входные данные из файла или из стандартного ввода?

Как можно написать скрипт, который принимает входные данные из аргумента имени файла или из стандартного ввода?

например, вы могли бы использовать less сюда. можно выполнить less filename и эквивалентно cat filename | less,

Есть ли простой способ "из коробки" сделать это? или мне нужно заново изобрести колесо и написать немного логики в скрипте?

8 ответов

Решение

Если аргумент файла является первым аргументом в вашем скрипте, проверьте, есть ли аргумент ($1) и что это файл. Еще прочитайте ввод от stdin -

Таким образом, ваш скрипт может содержать что-то вроде этого:

#!/bin/bash
[ $# -ge 1 -a -f "$1" ] && input="$1" || input="-"
cat $input

например, тогда вы можете назвать сценарий как

./myscript.sh filename

или же

who | ./myscript.sh

Изменить некоторые объяснения сценария:

[ $# -ge 1 -a -f "$1" ] - Если хотя бы один аргумент командной строки ($# -ge 1) И (оператор -a) первый аргумент - файл (-f проверяет, является ли файл "$1"), тогда результат теста равен true.

&& является логическим оператором оболочки. Если тест верен, тогда назначьте input="$1" а также cat $input выведет файл.

|| является логическим оператором оболочки ИЛИ Если тест ложен, то команды следующие || разобраны. вход назначен на "-". cat - читает с клавиатуры.

Подводя итог, если аргумент скрипта предоставлен и это файл, тогда переменной ввода назначается имя файла. Если нет допустимого аргумента, то cat читает с клавиатуры.

read читает со стандартного ввода. Перенаправление его из файла (./script <someinput) или через трубу (dosomething | ./script) не заставит его работать иначе.

Все, что вам нужно сделать, это перебрать все строки на входе (и это не отличается от перебора строк в файле).

(пример кода, обрабатывает только одну строку)

#!/bin/bash

read var
echo $var

Будет отображаться первая строка вашего стандартного ввода (либо через < или же |).

Вы не упоминаете, какую оболочку вы планируете использовать, поэтому я буду использовать bash, хотя это довольно стандартные вещи для всех оболочек.

Аргументы файла

Аргументы могут быть доступны через переменные $1 - $n ($0 возвращает команду, использованную для запуска программы). Скажем, у меня есть сценарий, который просто cat выводит количество файлов с разделителем между ними:

#!/usr/bin/env bash
#
# Parameters:
#    1:   string delimiter between arguments 2-n
#    2-n: file(s) to cat out
for arg in ${@:2} # $@ is the array of arguments, ${@:2} slices it starting at 2.
do
   cat $arg
   echo $1
done

В этом случае мы передаем имя файла в cat. Однако, если вы хотите преобразовать данные в файл (без явной записи и перезаписи), вы также можете сохранить содержимое файла в переменной:

file_contents=$(cat $filename)
[...do some stuff...]
echo $file_contents >> $new_filename

Читать со стандартного ввода

Что касается чтения из стандартного ввода, большинство оболочек имеют довольно стандартный read встроенный, хотя есть различия в том, как указаны подсказки (по крайней мере).

На странице руководства Bash buildins есть довольно краткое объяснение read, но я предпочитаю страницу Bash Hackers.

Просто:

read var_name

Несколько переменных

Чтобы установить несколько переменных, просто укажите несколько имен параметров для read:

read var1 var2 var3

read затем поместит одно слово из stdin в каждую переменную, сбросив все оставшиеся слова в последнюю переменную.

λ read var1 var2 var3
thing1 thing2 thing3 thing4 thing5
λ echo $var1; echo $var2; echo $var3
thing1
thing2
thing3 thing4 thing5

Если введено меньше слов, чем переменных, оставшиеся переменные будут пустыми (даже если они были установлены ранее):

λ read var1 var2 var3
thing1 thing2
λ echo $var1; echo $var2; echo $var3
thing1
thing2
# Empty line

Запрашивает

я использую -p часто отмечать для подсказки:

read -p "Enter filename: " filename

Примечание: ZSH и KSH (и, возможно, другие) используют другой синтаксис для запросов:

read "filename?Enter filename: " # Everything following the '?' is the prompt

Значения по умолчанию

Это не совсем read трюк, но я часто использую его в сочетании с read, Например:

read -p "Y/[N]: " reply
reply=${reply:-N}

В принципе, если переменная (reply) существует, верните себя, но если она пуста, верните следующий параметр ("N").

Вы также можете сделать:

#!/usr/bin/env bash

# Set variable input_file to either $1 or /dev/stdin, in case $1 is empty
# Note that this assumes that you are expecting the file name to operate on on $1
input_file="${1:-/dev/stdin}"

# You can now use "$input_file" as your file to operate on
cat "$input_file"

Более подробные приемы подстановки параметров в Bash см. Здесь.

Самый простой способ - перенаправить stdin самостоятельно:

if [ "$1" ] ; then exec < "$1" ; fi

Или, если вы предпочитаете более краткую форму:

test "$1" && exec < "$1"

Теперь остальная часть вашего скрипта может просто читать со стандартного ввода. Конечно, вы можете сделать то же самое с более сложным синтаксическим анализом, а не жестко кодировать положение имени файла как "$1",

Использовать (или цепочку) что-то еще, что уже ведет себя таким образом, и использовать "$@"

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

tr это самый очевидный способ сделать это, но он принимает только стандартный ввод, поэтому мы должны cat:

$ cat entab1.sh
#!/bin/sh

cat "$@"|tr -s ' ' '\t'
$ cat entab1.sh|./entab1.sh
#!/bin/sh

cat     "$@"|tr -s      '       '       '\t'
$ ./entab1.sh entab1.sh
#!/bin/sh

cat     "$@"|tr -s      '       '       '\t'
$ 

Например, когда используемый инструмент ведет себя так, мы можем переопределить это с помощью sed вместо:

$ cat entab2.sh
#!/bin/sh

sed -r 's/ +/\t/g' "$@"
$ 

Вы также можете сделать это простым и использовать этот код


Когда вы создаете файл сценария pass_it_on.sh с этим кодом,

#!/bin/bash

cat

Вы можете запустить

cat SOMEFILE.txt | ./pass_it_on.sh

и все содержимое стандартного ввода будет просто выбрасываться на экран.


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

#!/bin/bash

tmpFile=`mktemp`
cat > $tmpFile
cat $tmpFile    

и вот еще один пример, возможно, более читаемый, объясненный здесь:

http://mockingeye.com/blog/2013/01/22/reading-everything-stdin-in-a-bash-script/

#!/bin/bash

VALUE=$(cat)

echo "$VALUE"

Повеселись.

RaamEE

Самый простой способ и POSIX-совместимый:

file=${1--}

что эквивалентно ${1:--},

Затем прочитайте файл как обычно:

while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")
Другие вопросы по тегам