Понимание подстановки команд чтения файлов в Bash

11

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

$(< "$FILE")

Согласно странице руководства Bash, это эквивалентно:

$(cat "$FILE")

и я могу следовать линии рассуждений для этой второй строки. Bash выполняет раскрытие переменной $FILE, вводит подстановку команды, передает значение $FILEto cat, cat выводит содержимое $FILEна стандартный вывод, подстановка команды завершается заменой всей строки стандартным выводом, полученным из команды внутри, и Bash пытается выполнить ее следующим образом: простая команда.

Однако для первой строки, о которой я упоминал выше, я понимаю это так: Bash выполняет подстановку переменных $FILE, Bash открывается $FILEдля чтения на стандартном вводе, каким-то образом стандартный ввод копируется в стандартный вывод , подстановка команд завершается, и Bash пытается выполнить результирующий стандарт. вывод.

Может кто-нибудь объяснить мне, как содержимое $FILEот стандартного ввода к стандартному выводу?

Стэнли Ю.
источник

Ответы:

-3

<Не является непосредственно аспектом замещения Баш команды . Это оператор перенаправления (например, конвейер), который некоторые оболочки разрешают без команды (POSIX не определяет это поведение).

Возможно, это было бы более понятно с большим количеством пробелов:

echo $( < $FILE )

это фактически * то же самое, что и более POSIX-безопасный

echo $( cat $FILE )

... что также эффективно *

echo $( cat < $FILE )

Давайте начнем с этой последней версии. Это работает catбез аргументов, что означает, что он будет читать из стандартного ввода. $FILEперенаправляется на стандартный ввод из-за <, поэтому catпомещает его содержимое в стандартный вывод. Затем $(command)подстановка выводит catвыходные данные в аргументы для echo.

В bash(но не в стандарте POSIX), вы можете использовать <без команды. bash(и , zshи , kshно не dash) будет интерпретировать это как если бы cat <, хотя и не вызывая новый подпроцесс. Поскольку это встроено в оболочку, это быстрее, чем буквальное выполнение внешней команды cat. * Вот почему я говорю «фактически так же, как».

Адам Кац
источник
Итак, в последнем абзаце, когда вы говорите « bashбудет интерпретировать это как cat filename», вы имеете в виду, что это поведение относится к подстановке команд? Потому что, если я запускаю < filenameсам, bash не догонит его. Он ничего не выдаст и вернет меня обратно к приглашению.
Стэнли Ю.
Команда все еще нужна. @cuonglm изменил мой оригинальный текст, cat < filenameпротив cat filenameкоторого я выступаю, и могу вернуться.
Адам Кац
1
Труба - это тип файла. Оператор оболочки |создает канал между двумя подпроцессами (или, с некоторыми оболочками, из подпроцесса в стандартный ввод оболочки). Оператор оболочки $(…)создает канал от подпроцесса к самой оболочке (не к его стандартному вводу). Оператор оболочки <не использует конвейер, он только открывает файл и перемещает дескриптор файла в стандартный ввод.
Жиль "ТАК - перестань быть злым"
3
< fileэто не то же самое, что и cat < file(кроме случаев, zshкогда это похоже $READNULLCMD < file). < fileидеально подходит для POSIX и просто открывает fileдля чтения, а затем ничего не делает (так fileчто близко сразу). Это $(< file)или `< file`это специальный оператор ksh, zshи bash(и поведение не определено в POSIX). Смотрите мой ответ для деталей.
Стефан
2
Чтобы поместить комментарий @ StéphaneChazelas в другом свете: в первом приближении, $(cmd1) $(cmd2)как правило, будет то же самое, что и $(cmd1; cmd2). Но посмотрите на тот случай, когда cmd2есть < file. Если мы говорим $(cmd1; < file), файл не читается, но, с $(cmd1) $(< file), это так. Поэтому неправильно говорить, $(< file)что это обычный случай $(command)с командой < file.   $(< …)это особый случай подстановки команд, а не обычное использование перенаправления.
Скотт
14

$(<file)(также работает с `<file`) - специальный оператор оболочки Korn, скопированный с помощью zshи bash. Это действительно похоже на подстановку команд, но это не совсем так.

В оболочках POSIX простая команда:

< file var1=value1 > file2 cmd 2> file3 args 3> file4

Все части являются необязательными, вы можете иметь только перенаправления, только команды, только назначения или комбинации.

Если есть перенаправления, но нет команды, перенаправления выполняются (то есть, > fileони открываются и усекаются file), но тогда ничего не происходит. Так

< file

Открывается fileдля чтения, но тогда ничего не происходит, так как нет команды. Так что fileтогда закрыто и все. Если бы $(< file)была простая подстановка команд , то она бы расширилась до нуля.

В спецификации POSIX , в $(script), если scriptсостоит только из перенаправлений, что дает неопределенный результат . Это позволяет этому особому поведению оболочки Korn.

В ksh (здесь протестировано с ksh93u+), если скрипт состоит из одной и только одной простой команды (хотя комментарии разрешены до и после), которая состоит только из перенаправлений (без команды, без назначения) и если первое перенаправление является stdin (fd 0) только перенаправление ввода <, <<или <<<:

  • $(< file)
  • $(0< file)
  • $(<&3)(также $(0>&3)фактически, поскольку это - тот же самый оператор)
  • $(< file > foo 2> $(whatever))

но нет:

  • $(> foo < file)
  • ни $(0<> file)
  • ни $(< file; sleep 1)
  • ни $(< file; < file2)

затем

  • все, кроме первого перенаправления игнорируются (они анализируются)
  • и он расширяется до содержимого файла / heredoc / herestring (или чего угодно, что может быть прочитано из файлового дескриптора, если используются такие вещи <&3), минуя завершающие символы новой строки.

как будто используя, $(cat < file)кроме этого

  • чтение выполняется внутри оболочки, а не cat
  • ни труба, ни дополнительный процесс не вовлечены
  • как следствие вышесказанного, поскольку код внутри не запускается в подоболочке, любые изменения остаются после него (как в $(<${file=foo.txt})или $(<file$((++n))))
  • Ошибки чтения (но не ошибки при открытии файлов или дублировании файловых дескрипторов) игнорируются.

В zshэто же самое , за исключением того , что особое поведение срабатывает только , когда есть только один вход Перенаправление файл ( <fileили 0< file, нет <&3, <<<here, < a < b...)

Тем не менее, за исключением случаев эмуляции других оболочек, в:

< file
<&3
<<< here...

то есть, когда есть только входные перенаправления без команд, вне подстановки команд zshзапускается $READNULLCMD(пейджер по умолчанию), а при наличии как входных, так и выходных перенаправлений $NULLCMD( catпо умолчанию), так что даже если $(<&3)он не распознается как специальный оператор, он все равно будет работать как в ksh, вызывая для этого пейджер (этот пейджер действует так, catкак его stdout будет конвейером).

Однако в то время как ksh«s $(< a < b)будет расширяться на содержание a, в zsh, она расширяется к содержанию aи b(или только bесли multiosопция выключена), $(< a > b)будет копировать aв bи расширять ничего, и т.д.

bash имеет похожий оператор, но с некоторыми отличиями:

  • комментарии разрешены до, но не после:

    echo "$(
       # getting the content of file
       < file)"

    работает но:

    echo "$(< file
       # getting the content of file
    )"

    расширяется в ничто.

  • как в zshтолько один файл STDIN Перенаправление, хотя нет никакого падения назад к $READNULLCMD, так $(<&3), $(< a < b)действительно выполнять переадресацию , но расширить ничего.

  • по какой-то причине, хотя bashи не вызывает cat, он все еще разветвляется процесс, который передает содержимое файла через канал, что делает его гораздо меньше оптимизации, чем в других оболочках. По сути, это как $(cat < file)где catбы был встроен cat.
  • как следствие вышеизложенного, любое изменение, внесенное в него, впоследствии теряется (например, в $(<${file=foo.txt})упомянутом выше случае это $fileназначение теряется впоследствии).

In bash, IFS= read -rd '' var < file (также работает в zsh) - более эффективный способ чтения содержимого текстового файла в переменную. Это также имеет преимущество сохранения конечных символов новой строки. Смотрите также $mapfile[file]в zshzsh/mapfileмодуле и только для обычных файлов), который также работает с двоичными файлами.

Обратите внимание, что варианты на основе pdksh kshимеют несколько вариаций по сравнению с ksh93. Интересно, что mksh(одна из этих оболочек, полученных из pdksh), в

var=$(<<'EOF'
That's multi-line
test with *all* sorts of "special"
characters
EOF
)

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

Быть переносимым на все версии ksh, zshи bash, лучше всего, ограничиваться только $(<file)избеганием комментариев и помнить, что изменения переменных, сделанные внутри, могут сохраняться или не сохраняться.

Стефан Шазелас
источник
Это правильно, что $(<)это оператор на имена файлов? Является ли <в качестве $(<)оператора перенаправления, или не является оператором самостоятельно, и должен быть частью всего оператора $(<)?
Тим
@ Тим, неважно, как ты хочешь их назвать. $(<file)предназначен для расширения до содержания fileтаким же образом, как $(cat < file)и. Как это делается, зависит от оболочки к оболочке, что подробно описано в ответе. Если хотите, вы можете сказать, что это специальный оператор, который запускается, когда то, что выглядит как подстановка команды (синтаксически), содержит то, что выглядит как одно перенаправление стандартного ввода (синтаксически), но опять же с предостережениями и вариациями в зависимости от оболочки, как указано здесь ,
Стефан Шазелас
@ StéphaneChazelas: завораживающий, как обычно; Я отметил это. Так n<&mи n>&mделать то же самое? Я этого не знал, но, думаю, это не слишком удивительно.
Скотт
@ Скотт, да, они оба делают dup(m, n). Я вижу некоторые доказательства того, что ksh86 использует stdio, а некоторые fdopen(fd, "r" or "w"), возможно, имели значение тогда. Но использование stdio в оболочке не имеет большого смысла, поэтому я не ожидаю, что вы найдете какую-либо современную оболочку, в которой это будет иметь значение. Отличие заключается в том, что >&nэто dup(n, 1)(сокращение 1>&n), в то время как <&nэто dup(n, 0)(сокращенно 0<&n).
Стефан
Правильно. За исключением, конечно, формы с двумя аргументами вызова дублирования дескриптора файла dup2(); dup()принимает только один аргумент и, как open(), использует самый низкий доступный дескриптор файла. (Сегодня я узнал, что есть dup3()функция .)
Скотт
8

Потому что bashделает это для вас внутренне, расширяет имя файла и выводит файл на стандартный вывод, как если бы вы это делали $(cat < filename). Это особенность bash, может быть, вам нужно изучить bashисходный код, чтобы точно знать, как он работает.

Вот функция для обработки этой функции (из bashисходного кода, файла builtins/evalstring.c):

/* Handle a $( < file ) command substitution.  This expands the filename,
   returning errors as appropriate, then just cats the file to the standard
   output. */
static int
cat_file (r)
     REDIRECT *r;
{
  char *fn;
  int fd, rval;

  if (r->instruction != r_input_direction)
    return -1;

  /* Get the filename. */
  if (posixly_correct && !interactive_shell)
    disallow_filename_globbing++;
  fn = redirection_expand (r->redirectee.filename);
  if (posixly_correct && !interactive_shell)
    disallow_filename_globbing--;

  if (fn == 0)
    {
      redirection_error (r, AMBIGUOUS_REDIRECT);
      return -1;
    }

  fd = open(fn, O_RDONLY);
  if (fd < 0)
    {
      file_error (fn);
      free (fn);
      return -1;
    }

  rval = zcatfd (fd, 1, fn);

  free (fn);
  close (fd);

  return (rval);
}

Примечание, $(<filename)которое не совсем эквивалентно $(cat filename); последний потерпит неудачу, если имя файла начинается с тире -.

$(<filename)был изначально из ksh, и был добавлен bashиз Bash-2.02.

cuonglm
источник
1
cat filenameпотерпит неудачу, если имя файла начинается с тире, потому что cat принимает параметры. Вы можете обойти это на большинстве современных систем cat -- filename.
Адам Кац
-1

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

Вывод команд можно использовать в качестве аргументов для другой команды, для установки переменной и даже для генерации списка аргументов в цикле for.

foo=$(echo "bar")установит значение переменной $fooв bar; вывод команды echo bar.

Подстановка команд

iyrin
источник
1
Я считаю, что из вопроса довольно ясно, что ФП понимает основы подстановки команд; вопрос о частном случае $(< file), и он не нуждается в учебнике по общему случаю. Если вы говорите, $(< file)что это обычный случай $(command)с командой < file, то вы говорите то же самое, что говорит Адам Кац , и вы оба ошибаетесь.
Скотт