Как читать stdin, когда аргументы не передаются?

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

Я пробовал это:

if [ ! -n $1 ] # check if argument exists
   then
   $1=$(</dev/stdin)  # if not use stdin as an argument
   fi

var="$1"
while read line
   do
   ...                # find the longest line
   done <"$var"

Ответ 1

Просто замените bash специально интерпретированный /dev/stdin как имя файла:

VAR=$1
while read blah; do
  ...
done < "${VAR:-/dev/stdin}"

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

Ответ 2

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

$ echo param | script.sh
$ script.sh param

script.sh

#!/bin/bash

set -- "${1:-$(</dev/stdin)}" "${@:2}"

echo $1

Ответ 3

Переменные присваиваются значениям Var=Value, и эта переменная используется, например. echo $Var. В вашем случае это будет

1=$(</dev/stdin)

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

Ответ 4

Вот моя версия script:

#!/bin/bash
file=${1--} # POSIX-compliant; ${1:--} can be used either.
while IFS= read -r line; do
  printf '%s\n' "$line"
done < <(cat -- "$file")

Если файл отсутствует в аргументе, прочитайте его со стандартного ввода.

Дополнительные примеры: Как читать из файла или stdin в bash? в stackoverflow SE

Ответ 5

ответ пиквера обеспечивает элегантное решение; это объяснение , почему подход OP не работал.

Основная проблема с подходом OP заключалась в попытке назначить позиционный параметр $1 с помощью $1=..., который не будет работать.

LHS расширяется оболочкой до значения $1, и результат интерпретируется как имя переменной, которой нужно назначить, - явно, а не намерение.

единственный способ назначить $1 в bash через set встроенный. Предостережение состоит в том, что set неизменно устанавливает все позиционные параметры, поэтому вы должны включить и другие, если они есть.

set -- "${1:-/dev/stdin}" "${@:2}"     # "${@:2}" expands to all remaining parameters

(Если вы ожидаете только не более 1 аргумента, set -- "${1:-/dev/stdin}" будет делать.)

Вышеупомянутая также исправляет вторичную проблему с OP-подходом: попытка сохранить содержимое, а не имя файла stdin в $1, так как используется <.

${1:-/dev/stdin} представляет собой приложение bash расширение параметра, в котором говорится: вернуть значение $1, если $1 undefined (аргумент не передан) или его значение - пустая строка ("" или ''). Изменение ${1-/dev/stdin} (no :) вернет только /dev/stdin, если $1 - undefined (если оно содержит любое значение, даже пустую строку, оно будет возвращено).

Если собрать все вместе:

# Default to filename '/dev/stdin' (stdin), if none was specified.
set -- "${1:-/dev/stdin}" "${@:2}"

while read -r line; do
   ...                # find the longest line
done < "$1"

Но, конечно, гораздо более простой подход заключался бы в том, чтобы использовать ${1:-/dev/stdin} как имя файла напрямую:

while read -r line; do
   ...                # find the longest line
done < "${1:-/dev/stdin}"

или через промежуточную переменную:

filename=${1:-/dev/stdin}
while read -r line; do
   ...                # find the longest line
done < "$filename"