Zrozumienie zastępowania polecenia Bash do odczytu pliku

11

Próbuję zrozumieć, jak dokładnie Bash traktuje następujący wiersz:

$(< "$FILE")

Według strony podręcznika Bash jest to równoważne z:

$(cat "$FILE")

i mogę podążać za rozumowaniem tej drugiej linii. Bash wykonuje rozszerzanie zmiennej $FILE, wprowadza podstawienie polecenia, przekazuje wartość $FILEdo cat, cat przekazuje zawartość $FILEstandardowego wyjścia, podstawianie polecenia kończy się przez zastąpienie całej linii standardowym wyjściem wynikającym z polecenia wewnątrz, a Bash próbuje wykonać to jak proste polecenie.

Jednak w pierwszym wierszu, o którym wspomniałem powyżej, rozumiem to jako: Bash wykonuje podstawienie zmiennej $FILE, Bash otwiera się $FILEdo odczytu na standardowym wejściu, w jakiś sposób standardowe wejście jest kopiowane na standardowe wyjście , podstawianie poleceń kończy się, a Bash próbuje wykonać wynikowy standard wynik.

Czy ktoś może mi wyjaśnić, w jaki sposób zawartość $FILEzmienia się ze standardowego na standardowe?

Stanley Yu
źródło

Odpowiedzi:

-3

Nie <jest to bezpośrednio aspekt zastępowania poleceń bash . Jest to operator przekierowania (jak potok), na co niektóre powłoki zezwalają bez polecenia (POSIX nie określa tego zachowania).

Być może byłoby to bardziej jasne przy większej ilości spacji:

echo $( < $FILE )

jest to efektywnie * to samo, co bardziej bezpieczne dla POSIX

echo $( cat $FILE )

... co również skutecznie *

echo $( cat < $FILE )

Zacznijmy od ostatniej wersji. Działa catbez żadnych argumentów, co oznacza, że ​​będzie czytać ze standardowego wejścia. $FILEjest przekierowywany do standardowego wejścia z powodu <, więc catumieszcza jego zawartość na standardowym wyjściu. $(command)Subsitution następnie wypycha cat„s wyjście do argumentów echo.

W bash(ale nie w standardzie POSIX) możesz używać <bez polecenia. bash(a zsh, a kshjednak nie dash) zinterpretuje, że jakby cat <, choć bez powołując nową podproces. Ponieważ jest to natywne dla powłoki, jest szybsze niż dosłowne uruchomienie zewnętrznego polecenia cat. * Dlatego mówię „efektywnie tak samo jak”.

Adam Katz
źródło
Więc w ostatnim akapicie, kiedy mówisz „ bashzinterpretuje to jako cat filename”, czy masz na myśli, że to zachowanie jest specyficzne dla zastępowania poleceń? Bo jeśli uruchomię < filenamesię sam, bash go nie wykręci. Nic nie wyświetli i nie wróci do monitu.
Stanley Yu,
Polecenie jest nadal potrzebne. @cuonglm zmieniany mój oryginalny tekst z cat < filenamedo cat filenamektórej jestem przeciwny i może powrócić.
Adam Katz
1
Rura jest rodzajem pliku. Operator powłoki |tworzy potok między dwoma podprocesami (lub, w przypadku niektórych powłok, od podprocesu do standardowego wejścia powłoki). Operator powłoki $(…)tworzy potok z podprocesu do samej powłoki (nie do standardowego wejścia). Operator powłoki <nie angażuje potoku, jedynie otwiera plik i przenosi deskryptor pliku na standardowe wejście.
Gilles „SO- przestań być zły”
3
< filenie jest taki sam jak cat < file(z wyjątkiem tego, zshgdzie jest $READNULLCMD < file). < filejest idealnie POSIX i po prostu otwiera się filedo czytania, a następnie nic nie robi (więc filejest od razu blisko). To $(< file)czy `< file`jest to specjalny operator ksh, zshi bash(a zachowanie pozostaje nieokreślona w POSIX). Zobacz moją odpowiedź, aby poznać szczegóły.
Stéphane Chazelas
2
Aby umieścić komentarz @ StéphaneChazelas w innym świetle: do pierwszego przybliżenia, $(cmd1) $(cmd2)zwykle będzie taki sam jak $(cmd1; cmd2). Ale spójrz na przypadek, w którym cmd2jest < file. Jeśli mówimy $(cmd1; < file), plik nie jest odczytywany, ale z $(cmd1) $(< file)nim jest. Nie można więc powiedzieć, że $(< file)jest to zwykły przypadek $(command)polecenia < file.   $(< …)jest szczególnym przypadkiem zastępowania poleceń, a nie normalnym użyciem przekierowania.
Scott
14

$(<file)(działa również z `<file`) jest specjalnym operatorem powłoki Korna skopiowanym przez zshi bash. Wygląda bardzo podobnie do zastępowania poleceń, ale tak naprawdę nie jest.

W powłokach POSIX proste polecenie to:

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

Wszystkie części są opcjonalne, możesz mieć tylko przekierowania, tylko polecenia, tylko przypisanie lub kombinacje.

Jeśli istnieją przekierowania, ale nie ma polecenia, przekierowania są wykonywane (więc > fileotworzy się i obcina file), ale wtedy nic się nie dzieje. Więc

< file

Otwiera się filedo odczytu, ale wtedy nic się nie dzieje, ponieważ nie ma polecenia. Więc to filejest zamknięte i to wszystko. Gdyby $(< file)proste zastąpienie polecenia , rozwinęłoby się do zera.

W specyfikacji POSIX , w $(script), jeśli scriptskłada się tylko z przekierowań, daje nieokreślone wyniki . To pozwala na to specjalne zachowanie skorupy Korna.

W ksh (tutaj testowane z ksh93u+), jeśli skrypt składa się z jednego i tylko jednego prostego polecenia (chociaż komentarze są dozwolone przed i po), który składa się tylko z przekierowań (bez polecenia, bez przypisania) i jeśli pierwszym przekierowaniem jest stdin (fd 0) tylko wejście ( <, <<i <<<) przekierowania sposób:

  • $(< file)
  • $(0< file)
  • $(<&3)(również w $(0>&3)rzeczywistości, ponieważ w rzeczywistości jest to ten sam operator)
  • $(< file > foo 2> $(whatever))

ale nie:

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

następnie

  • wszystkie oprócz pierwszego przekierowania są ignorowane (są analizowane)
  • i rozwija się do zawartości pliku / heredoc / herestring (lub czegokolwiek, co można odczytać z deskryptora pliku, jeśli używa się takich rzeczy <&3), minus końcowe znaki nowego wiersza.

jakbyś za $(cat < file)wyjątkiem tego

  • odczyt jest wykonywany wewnętrznie przez powłokę, a nie przez cat
  • nie jest wymagana żadna rura ani dodatkowy proces
  • w wyniku powyższego, ponieważ kod wewnątrz nie jest uruchamiany w podpowłoce, wszelkie modyfikacje pozostają później (jak w $(<${file=foo.txt})lub $(<file$((++n))))
  • błędy odczytu (choć nie błędy podczas otwierania plików lub powielania deskryptorów plików) są dyskretnie ignorowane.

W zsh, to samo z wyjątkiem tego, że szczególną zachowanie jest wyzwalany tylko wtedy, gdy istnieje tylko jeden plik wejściowy przekierowania ( <filelub 0< filenie <&3, <<<here, < a < b...)

Jednak z wyjątkiem emulacji innych powłok, w:

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

oznacza to, że istnieją tylko przekierowania wejściowe bez poleceń, poza podstawianiem poleceń, zshuruchamia $READNULLCMD(domyślnie pager), a gdy są przekierowania wejściowe i wyjściowe, $NULLCMD( catdomyślnie), więc nawet jeśli $(<&3)nie jest to rozpoznawane jako specjalne operator nadal będzie działał tak jak w tym kshprzypadku, wywołując do tego pager (ten pager zachowuje się tak, jakby catjego standardowe wyjście było potokiem).

Jednak podczas gdy ksh„s $(< a < b)rozwinie się do treści a, w zsh, rozszerza się do treści ai b(lub tylko bwtedy, gdy multiosopcja jest wyłączona), $(< a > b)by skopiować ado bi rozwinąć do niczego, itd.

bash ma podobny operator, ale z kilkoma różnicami:

  • komentarze są dozwolone przed, ale nie po:

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

    działa, ale:

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

    rozwija się do zera.

  • jak się zshtylko jeden plik stdin przekierowania, choć nie ma spaść z powrotem do $READNULLCMD, tak $(<&3), $(< a < b)nie wykonywać przekierowań ale poszerzyć do niczego.

  • z jakiegoś powodu, mimo bashże nie wywołuje cat, nadal rozwidla proces, który przekazuje zawartość pliku przez potok, dzięki czemu jest mniej optymalizacyjny niż w innych powłokach. Jest to w istocie niczym $(cat < file)gdzie catbyłaby wbudowane cat.
  • w wyniku powyższego wszelkie zmiany dokonane wewnątrz są następnie tracone (w $(<${file=foo.txt})wyżej wspomnianym na przykład $fileprzypisanie jest tracone później).

W bash, IFS= read -rd '' var < file (działa także w zsh) jest bardziej skuteczny sposób, aby odczytać zawartość tekstu pliku do zmiennej. Ma także tę zaletę, że zachowuje końcowe znaki nowego wiersza. Zobacz także $mapfile[file]w zsh(w zsh/mapfilemodule i tylko dla zwykłych plików), który działa również z plikami binarnymi.

Zauważ, że warianty oparte na pdksh kshmają kilka odmian w porównaniu do ksh93. Interesujące w mksh(jednej z tych powłok pochodzących z pdksh), w

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

jest zoptymalizowany pod tym względem, że zawartość tego dokumentu (bez znaków końcowych) jest rozszerzana bez użycia pliku tymczasowego lub potoku, jak ma to miejsce w przypadku dokumentów tutaj, co czyni go efektywną składnią wielowierszowego cytowania.

Aby być przenośnym dla wszystkich wersji ksh, zsha bashnajlepiej ograniczyć się tylko do $(<file)unikania komentarzy i pamiętać, że modyfikacje dokonanych w nich zmiennych mogą, ale nie muszą zostać zachowane.

Stéphane Chazelas
źródło
Czy to prawda, że $(<)jest operatorem nazw plików? Czy <w $(<)operatora przekierowania, czy nie operator na własną rękę, i muszą być częścią całego operatora $(<)?
Tim
@Tim, nie ma znaczenia, jak chcesz do nich zadzwonić. $(<file)ma na celu rozszerzenie do treści filew podobny sposób $(cat < file). To, jak to się robi, różni się w zależności od powłoki, co opisano szczegółowo w odpowiedzi. Jeśli chcesz, możesz powiedzieć, że jest to specjalny operator, który jest uruchamiany, gdy coś, co wygląda jak podstawienie polecenia (składniowo) zawiera coś, co wygląda jak pojedyncze przekierowanie standardowego wejścia (składniowo), ale znowu z zastrzeżeniami i odmianami zależnymi od powłoki wymienionymi tutaj .
Stéphane Chazelas
@ StéphaneChazelas: Jak zwykle fascynująca; Dodałem to do zakładek. A więc n<&mi n>&mzrobić to samo? Nie wiedziałem o tym, ale chyba nie jest to zaskakujące.
Scott
@ Scott, tak, oboje robią dup(m, n). Widzę pewne dowody na użycie ksh86 przy użyciu stdio i niektóre fdopen(fd, "r" or "w"), więc mogło to mieć znaczenie. Ale używanie stdio w powłoce nie ma większego sensu, więc nie oczekuję, że znajdziesz jakąkolwiek nowoczesną powłokę, w której to coś zmieni. Jedną różnicą jest to, że >&njest dup(n, 1)(skrót od 1>&n), podczas gdy <&njest dup(n, 0)(skrót od 0<&n).
Stéphane Chazelas,
Dobrze. Z wyjątkiem, oczywiście, wywoływana jest dwuelementowa forma wywołania duplikacji deskryptora pliku dup2(); dup()pobiera tylko jeden argument i, na przykład open(), używa najniższego dostępnego deskryptora pliku. (Dzisiaj dowiedziałem się, że jest jakaś dup3()funkcja ).
Scott
8

Ponieważ bashrobi to wewnętrznie dla ciebie, rozwinął nazwę pliku i przechowuje plik na standardowe wyjście, tak jakbyś to zrobił $(cat < filename). Jest to funkcja bash, być może trzeba zajrzeć do bashkodu źródłowego, aby dokładnie wiedzieć, jak to działa.

Oto funkcja do obsługi tej funkcji (z bashkodu źródłowego, pliku 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);
}

Uwaga, która $(<filename)nie jest dokładnie równoważna z $(cat filename); to drugie zawiedzie, jeśli nazwa pliku zaczyna się od myślnika -.

$(<filename)był pierwotnie od kshi dodano do bashz Bash-2.02.

Cuonglm
źródło
1
cat filenamezawiedzie, jeśli nazwa pliku zaczyna się od myślnika, ponieważ cat akceptuje opcje. Możesz obejść ten problem w większości nowoczesnych systemów cat -- filename.
Adam Katz,
-1

Pomyśl o zastąpieniu polecenia jak o zwykłym uruchomieniu polecenia i zrzuceniu danych wyjściowych w punkcie, w którym uruchamiasz polecenie.

Dane wyjściowe poleceń mogą być użyte jako argumenty innego polecenia, do ustawienia zmiennej, a nawet do generowania listy argumentów w pętli for.

foo=$(echo "bar")ustawi wartość zmiennej $foona bar; wyjście polecenia echo bar.

Zmiana polecenia

iyrin
źródło
1
Uważam, że dość jasne jest pytanie, że PO rozumie podstawy podstawiania poleceń; pytanie dotyczy szczególnego przypadku $(< file)i nie potrzebuje samouczka na temat ogólnego przypadku. Jeśli mówisz, że $(< file)to zwykły przypadek $(command)z rozkazem < file, to mówisz to samo, co mówi Adam Katz , i oboje się mylicie.
Scott