Розуміння заміни команди Баша на читання файлів


11

Я намагаюся зрозуміти, як саме Баш ставиться до наступного рядка:

$(< "$FILE")

За даними сторінки чоловіка Bash, це еквівалентно:

$(cat "$FILE")

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

Однак для першого рядка, про який я згадував вище, я розумію це як: Bash виконує заміну змінної $FILE, Bash відкривається $FILEдля читання на стандартному вході, якось стандартний вхід копіюється на стандартний вихід , підміна команд завершується, і Bash намагається виконати отриманий стандарт вихід.

Може хтось, будь ласка, пояснить мені, як вміст $FILEпереходить від stdin до stdout?

Відповіді:


-3

Це <не безпосередньо аспект підстановки команд bash . Це оператор перенаправлення (як труба), який деякі оболонки дозволяють без команди (POSIX не визначає цю поведінку).

Можливо, це було б зрозуміліше з більшою кількістю пробілів:

echo $( < $FILE )

це ефективно * те саме, що і більш POSIX-безпечно

echo $( cat $FILE )

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

echo $( cat < $FILE )

Почнемо з тієї останньої версії. Це працює catбез аргументів, це означає, що він буде прочитаний зі стандартного вводу. $FILEперенаправляється на стандартний вхід через <, тому catставить його вміст ставиться на стандартний вихід. Потім $(command)підстановка підштовхує catвихідний результат до аргументів для echo.

У bash(але не в стандарті POSIX) ви можете використовувати <без команди. bashzshта kshні dash) інтерпретуватиме це як би cat <, хоча без виклику нового підпроцесу. Оскільки це властиве оболонці, це швидше, ніж буквально виконувати зовнішню команду cat. * Ось чому я кажу "ефективно те саме, що".


Отже, в останньому абзаці, коли ви говорите " bashбуде інтерпретувати це як cat filename", ви маєте на увазі, що така поведінка специфічна для заміни команд? Тому що, якщо я біжу < filenameсама, баш це не виганяє. Він нічого не виведе і поверне мене до підказки.
Стенлі Ю.

Команда все ще потрібна. @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складається лише з перенаправлень, що дає неозначені результати . Це дозволяє дозволити особливу поведінку оболонки Корна.

У 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призначення втрачається згодом).

В bash, IFS= read -rd '' var < file (також працює в zsh) є більш ефективним способом зчитування вмісту текстового файлу в змінну. Це також має перевагу збереження останніх символів нового рядка. Дивіться також $mapfile[file]в zshzsh/mapfileмодулі та лише для звичайних файлів), який також працює з бінарними файлами.

Зауважимо, що варіанти на основі pdksh kshмають кілька варіацій порівняно з ksh93. Цікаво, що в mksh(одній із цих оболонок, отриманих пдкш), в

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

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

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


Чи правильно, що $(<)це оператор з іменами файлів? Перебуває <в $(<)операторі перенаправлення, чи не є оператором самостійно, і він повинен бути частиною всього оператора $(<)?
Тім

@Tim, не має великого значення, як ви хочете їх зателефонувати. $(<file)Мається на увазі розширення до змісту fileаналогічним чином, як це $(cat < file)було б. Як це робиться, залежить від оболонки до оболонки, яка детально описана у відповіді. Якщо вам подобається, ви можете сказати, що це спеціальний оператор, який спрацьовує, коли те, що схоже на підстановку команди (синтаксично) містить те, що схоже на перенаправлення одного синтаксису (синтаксично), але знову ж із застереженнями та варіаціями залежно від оболонки, як зазначено тут .
Стефан Шазелас

@ StéphaneChazelas: Захоплююче, як завжди; Я це зробив у закладках. Отже, n<&mі n>&mробити те саме? Я цього не знав, але, мабуть, це не надто дивно.
Скотт

@Scott, так, вони обоє роблять dup(m, n). Я бачу деякі докази ksh86 за допомогою stdio та деякі fdopen(fd, "r" or "w"), тому це, можливо, було мати значення тоді. Але використовувати stdio в оболонці мало сенсу, тому я не сподіваюся, що ви знайдете будь-яку сучасну оболонку, де це змінить значення. Одна відмінність полягає в тому, що >&nце dup(n, 1)(short for 1>&n), тоді як <&nє dup(n, 0)(short for 0<&n).
Стефан Шазелас

Правильно. За винятком, звичайно, виклику двох аргументів виклику дублювання дескриптора файлу виклику dup2(); dup()бере лише один аргумент і, як-от open(), використовує найнижчий доступний дескриптор файлу. (Сьогодні я дізнався, що є dup3()функція .)
Скотт,

8

Оскільки bashце все для вас внутрішньо, розширено ім'я файлу і позначає файл на стандартний вихід, як якщо б ви це робили $(cat < filename). Це баш-функція, можливо, вам потрібно буде вивчити 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.


1
cat filenameне вдасться, якщо ім'я файлу починається з тире, тому що кішка приймає параметри. З цим можна обійтись у більшості сучасних систем cat -- filename.
Адам Кац

-1

Подумайте про заміну команд як запуск команди, як зазвичай, і скидання виводу в точку, де ви виконуєте команду.

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

foo=$(echo "bar")встановить значення змінної $fooв bar; вихід команди echo bar.

Командна заміна


1
Я вважаю, що з питання досить зрозуміло, що ОП розуміє основи підстановки команд; питання стосується особливого випадку $(< file), і йому не потрібен підручник із загальної справи. Якщо ви говорите, що $(< file)це просто звичайний випадок $(command)із командою < file, ви говорите те саме, що говорить Адам Кац , і ви обидва помиляєтесь.
Скотт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.