Jak działa „cat << EOF” w bash?

629

Musiałem napisać skrypt, aby wprowadzić wieloliniowe dane wejściowe do programu ( psql).

Po trochę googlingu znalazłem następującą składnię:

cat << EOF | psql ---params
BEGIN;

`pg_dump ----something`

update table .... statement ...;

END;
EOF

To poprawnie konstruuje łańcuch wieloliniowy (od BEGIN;do END;włącznie) i potokuje go jako dane wejściowe do psql.

Ale nie mam pojęcia, jak / dlaczego to działa, czy ktoś mógłby wyjaśnić?

Mam na myśli przede wszystkim cat << EOF, znam dane >wyjściowe do pliku, >>dołącza się do pliku, <odczytuje dane wejściowe z pliku.

Co <<dokładnie robi?

I czy jest do tego strona podręcznika?

hasen
źródło
26
To prawdopodobnie bezużyteczne użycie cat. Spróbuj psql ... << EOF ... Zobacz także „ciągi tutaj”. mywiki.wooledge.org/BashGuide/InputAndOutput?#Here_Strings
Wstrzymano do odwołania.
1
Dziwię się, że działa z kotem, ale nie z echem. cat powinien oczekiwać nazwy pliku jako stdin, a nie łańcucha znaków. psql << EOF brzmi logicznie, ale nie inaczej. Działa z kotem, ale nie z echem. Dziwne zachowanie. Jakieś wskazówki na ten temat?
Alex
Odpowiadając sobie: cat bez parametrów wykonuje i replikuje na wyjściu cokolwiek wysyłane przez wejście (stdin), stąd użycie tego wyjścia do wypełnienia pliku przez>. W rzeczywistości nazwa pliku odczytywana jako parametr nie jest standardowym strumieniem.
Alex
@Alex echo po prostu wypisuje argumenty wiersza poleceń podczas catodczytywania polecenia stding (po przesłaniu do niego) lub odczytuje plik odpowiadający argumentom wiersza poleceń
The-null-Pointer-

Odpowiedzi:

517

Nazywa się to formatem heredoc , aby zapewnić ciąg do standardowego wejścia. Więcej informacji na stronie https://en.wikipedia.org/wiki/Here_document#Unix_shells .


Od man bash:

Tutaj Dokumenty

Ten typ przekierowania instruuje powłokę, aby odczytywała dane wejściowe z bieżącego źródła, dopóki nie zostanie wyświetlony wiersz zawierający tylko słowo (bez spacji końcowych).

Wszystkie linie odczytane do tego momentu są następnie używane jako standardowe wejście dla polecenia.

Format dokumentów tutaj to:

          <<[-]word
                  here-document
          delimiter

Na słowie nie jest wykonywana interpretacja parametrów, podstawianie poleceń, interpretacja arytmetyczna ani interpretacja nazw ścieżek . Jeśli cytowane są jakiekolwiek znaki w słowie , separator jest wynikiem usunięcia cudzysłowu w słowie , a wiersze w niniejszym dokumencie nie są rozwijane. Jeżeli słowo nie jest cytowane, wszystkie wiersze dokumentu tutaj podlegają interpretacji parametrów, podstawianiu poleceń i interpretacji arytmetycznej. W tym ostatnim przypadku, sekwencja znaków \<newline>jest ignorowana i \muszą być wykorzystane do zacytować znaki \, $i `.

Jeśli operatorem przekierowania jest <<-, to wszystkie wiodące znaki tabulacji są usuwane z linii wejściowych i linii zawierającej ogranicznik . Pozwala to na wcięcie dokumentów w skryptach powłoki w naturalny sposób.

kennytm
źródło
12
Najtrudniej mi było wyłączyć rozszerzanie zmiennych / parametrów. Wszystko, co musiałem zrobić, to użyć „podwójnych cudzysłowów” i to naprawiło! Dzięki za informację!
Xeoncross
11
W związku z tym <<-należy pamiętać, że tylko wiodące znaki tabulacji są usuwane - nie znaki tabulacji miękkiej. Jest to jeden z tych rzadkich przypadków, kiedy naprawdę potrzebujesz znaku tabulacji. Jeśli w pozostałej części dokumentu używane są miękkie tabulatory, pamiętaj, aby wyświetlać niewidoczne znaki oraz (np.) Kopiować i wklejać tabulatory. Jeśli zrobisz to dobrze, podświetlanie składni powinno poprawnie przechwytywać końcowy ogranicznik.
trkoch,
1
Nie rozumiem, w jaki sposób ta odpowiedź jest bardziej pomocna niż te poniżej. Zwraca jedynie informacje, które można znaleźć w innych miejscach (które prawdopodobnie zostały już sprawdzone)
BrDaHa
@BrDaHa, może nie jest. Dlaczego pytanie z powodu pozytywnych opinii? to był tylko jeden na kilka lat. widać to poprzez porównanie dat.
Aleksiej Martianow
501

cat <<EOFSkładnia jest bardzo przydatna podczas pracy z tekstem multi-line w bash, np. podczas przypisywania ciągu wielowierszowego do zmiennej powłoki, pliku lub potoku.

Przykłady cat <<EOFużycia składni w Bash:

1. Przypisz ciąg wieloliniowy do zmiennej powłoki

$ sql=$(cat <<EOF
SELECT foo, bar FROM db
WHERE foo='baz'
EOF
)

$sqlZmienna posiada obecnie znaki nowej linii zbyt. Możesz to zweryfikować za pomocą echo -e "$sql".

2. Przekaż ciąg wiersza do pliku w Bash

$ cat <<EOF > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
EOF

print.shPlik zawiera teraz:

#!/bin/bash
echo $PWD
echo /home/user

3. Prześlij ciąg wieloliniowy do rury w Bash

$ cat <<EOF | grep 'b' | tee b.txt
foo
bar
baz
EOF

b.txtPlik zawiera bari bazlinie. To samo wyjście jest drukowane na stdout.

Vojtech Vitek
źródło
1. 1 i 3 można wykonać bez kota; 2. Przykład 1 można wykonać prostym wieloliniowym łańcuchem
Daniel Alder
269

W twoim przypadku „EOF” jest znany jako „Here Tag”. Zasadniczo <<Hereinformuje powłokę, że zamierzasz wprowadzić ciąg wielowierszowy, aż do „znacznika” Here. Możesz nazwać ten tag, jak chcesz, często EOFlub STOP.

Niektóre zasady dotyczące tagów Tutaj:

  1. Znacznik może być dowolnym ciągiem, dużymi lub małymi literami, chociaż większość ludzi używa wielkich liter zgodnie z konwencją.
  2. Tag nie będzie uważany za tag Here, jeśli w tym wierszu znajdują się inne słowa. W takim przypadku będzie to po prostu uważane za część ciągu. Tag powinien być sam w osobnym wierszu, aby mógł być uważany za tag.
  3. Tag nie powinien mieć żadnych początkowych ani końcowych spacji w tym wierszu, aby można go było uznać za tag. W przeciwnym razie będzie uważany za część ciągu.

przykład:

$ cat >> test <<HERE
> Hello world HERE <-- Not by itself on a separate line -> not considered end of string
> This is a test
>  HERE <-- Leading space, so not considered end of string
> and a new line
> HERE <-- Now we have the end of the string
edelany
źródło
30
to jest najlepsza rzeczywista odpowiedź ... zdefiniujesz oba i jasno określisz główny cel zastosowania zamiast pokrewnej teorii ... co jest ważne, ale nie konieczne ... dzięki - super pomocne
oemb1905
5
@edelans, musisz dodać, że gdy <<-zostanie użyta, wiodąca karta nie zapobiegnie rozpoznaniu znacznika
The-null-Pointer-
1
twoja odpowiedź kliknęła mnie na „masz zamiar wprowadzić ciąg wieloliniowy”
Rachunek
79

POSIX 7

cytowany kennytm man bash, ale większość z nich to także POSIX 7: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04 :

Operatory przekierowania „<<” i „<< -” pozwalają na przekierowanie linii zawartych w pliku wejściowym powłoki, znanym jako „dokument tutaj”, na wejście polecenia.

Niniejszy dokument należy traktować jako pojedyncze słowo, które zaczyna się po następnym i trwa do momentu, aż pojawi się wiersz zawierający tylko ogranicznik ia, bez znaków pomiędzy nimi. Następnie rozpoczyna się następny dokument tutaj, jeśli taki istnieje. Format jest następujący:

[n]<<word
    here-document
delimiter

gdzie opcjonalne n oznacza numer deskryptora pliku. Jeśli numer zostanie pominięty, dokument tutaj odnosi się do standardowego wejścia (deskryptor pliku 0).

Jeżeli cytowany jest jakikolwiek znak w słowie, ogranicznik tworzy się poprzez usunięcie cudzysłowu ze słowem, a wiersze dokumentu tutaj nie będą rozszerzane. W przeciwnym razie separatorem będzie samo słowo.

Jeżeli nie podaje się żadnych słów w słowie, wszystkie wiersze niniejszego dokumentu należy rozwinąć w celu rozszerzenia parametrów, podstawienia poleceń i rozszerzenia arytmetycznego. W tym przypadku wejściowy zachowuje się jak wewnętrzne cudzysłowy (patrz Podwójne cudzysłowy). Jednak znak podwójnego cudzysłowu („” ”) nie będzie traktowany specjalnie w niniejszym dokumencie, z wyjątkiem sytuacji, gdy podwójny cudzysłów pojawia się w„ $ () ”,„ `` ”lub„ $ {} ”.

Jeżeli symbolem przekierowania jest „<< -”, wszystkie <tab>znaki wiodące zostaną usunięte z linii wejściowych i linii zawierającej ogranicznik końcowy. Jeżeli w wierszu podano więcej niż jeden operator „<<” lub „<< -”, dokument dołączony do pierwszego operatora zostanie dostarczony jako pierwszy przez aplikację i zostanie najpierw odczytany przez powłokę.

Kiedy niniejszy dokument jest odczytywany z urządzenia końcowego, a powłoka jest interaktywna, zapisuje zawartość zmiennej PS2, przetworzonej jak opisano w Zmiennych powłoki, do standardowego błędu przed odczytaniem każdego wiersza danych wejściowych, dopóki ogranicznik nie zostanie rozpoznany.

Przykłady

Niektórych przykładów jeszcze nie podano.

Cytaty zapobiegają rozszerzaniu parametrów

Bez cytatów:

a=0
cat <<EOF
$a
EOF

Wynik:

0

Z cytatami:

a=0
cat <<'EOF'
$a
EOF

lub (brzydkie, ale ważne):

a=0
cat <<E"O"F
$a
EOF

Wyjścia:

$a

Łącznik usuwa wiodące karty

Bez łącznika:

cat <<EOF
<tab>a
EOF

gdzie <tab>jest dosłowna karta i można ją wstawić za pomocąCtrl + V <tab>

Wynik:

<tab>a

Z łącznikiem:

cat <<-EOF
<tab>a
<tab>EOF

Wynik:

a

Jest to oczywiście po to, abyś mógł wciąć swój catkod otaczający, który jest łatwiejszy do odczytania i utrzymania. Na przykład:

if true; then
    cat <<-EOF
    a
    EOF
fi

Niestety nie działa to w przypadku znaków spacji: POSIX preferował tabtutaj wcięcia. Yikes.

Ciro Santilli
źródło
W ostatnim przykładzie omawiającym <<-i <tab>anależy zauważyć, że celem było umożliwienie normalnego wcięcia kodu w skrypcie, jednocześnie umożliwiając rozpoczęcie tekstu heredoc przedstawionego procesowi odbiorczemu w kolumnie 0. Nie jest to zbyt często spotykana funkcja i trochę więcej kontekstu może zapobiec
David C. Rankin
1
Jak uniknąć rozszerzenia, jeśli część treści między moimi znacznikami EOF wymaga rozszerzenia, a niektóre nie?
Jeanmichel Cote,
2
... po prostu użyj ukośnika przed$
Jeanmichel Cote,
@JeanmichelCote Nie widzę lepszej opcji :-) Przy zwykłych ciągach możesz również rozważyć pomieszanie cytatów "$a"'$b'"$c", ale tutaj nie ma analogii AFAIK.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
25

Używanie trójnika zamiast kota

Nie do końca jako odpowiedź na pierwotne pytanie, ale i tak chciałem się z tym podzielić: musiałem utworzyć plik konfiguracyjny w katalogu, który wymagałby uprawnień roota.

W tym przypadku nie działa:

$ sudo cat <<EOF >/etc/somedir/foo.conf
# my config file
foo=bar
EOF

ponieważ przekierowanie jest obsługiwane poza kontekstem sudo.

Zamiast tego użyłem tego:

$ sudo tee <<EOF /etc/somedir/foo.conf >/dev/null
# my config file
foo=bar
EOF
Andreas Maier
źródło
w twoim przypadku użyj sudo bash -c 'cat << EOF> /etc/somedir/foo.conf # mój plik konfiguracyjny foo = bar EOF'
podobnie
5

Małe rozszerzenie powyższych odpowiedzi. Trailing >kieruje dane wejściowe do pliku, zastępując istniejącą zawartość. Jednak szczególnie wygodnym zastosowaniem jest >>dołączona podwójna strzałka, która dodaje nową zawartość na końcu pliku, jak w:

cat <<EOF >> /etc/fstab
data_server:/var/sharedServer/authority/cert /var/sharedFolder/sometin/authority/cert nfs
data_server:/var/sharedServer/cert   /var/sharedFolder/sometin/vsdc/cert nfs
EOF

To wydłuża czas fstabbez martwienia się o przypadkową modyfikację dowolnej zawartości.

Lefty G. Balogh
źródło
1

To niekoniecznie jest odpowiedź na pierwotne pytanie, ale dzielenie się niektórymi wynikami z moich własnych testów. To:

<<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test

utworzy ten sam plik co:

cat <<test > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
test

Nie widzę więc sensu używania polecenia cat.


źródło
2
która skorupa Testowałem z bash 4.4 na Ubuntu 18.04, a także bash 3.2 na OSX. Zarówno stworzony pusty plik przy użyciu tylko <<testbez cat <<test.
wisbucky,
To działało dla mnie na LInux Mint 19 Tara w zsh
Geoff Langenderfer
0

Warto zauważyć, że tutaj dokumenty działają również w pętlach bash. Ten przykład pokazuje, jak uzyskać listę kolumn tabeli:

export postgres_db_name='my_db'
export table_name='my_table_name'

# start copy 
while read -r c; do test -z "$c" || echo $table_name.$c , ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
SELECT column_name
FROM information_schema.columns
WHERE 1=1
AND table_schema = 'public'
AND table_name   =:'table_name'  ;
EOF
)
# stop copy , now paste straight into the bash shell ...

output: 
my_table_name.guid ,
my_table_name.id ,
my_table_name.level ,
my_table_name.seq ,

lub nawet bez nowej linii

while read -r c; do test -z "$c" || echo $table_name.$c , | perl -ne 
's/\n//gm;print' ; done < <(cat << EOF | psql -t -q -d $postgres_db_name -v table_name="${table_name:-}"
 SELECT column_name
 FROM information_schema.columns
 WHERE 1=1
 AND table_schema = 'public'
 AND table_name   =:'table_name'  ;
 EOF
 )

 # output: daily_issues.guid ,daily_issues.id ,daily_issues.level ,daily_issues.seq ,daily_issues.prio ,daily_issues.weight ,daily_issues.status ,daily_issues.category ,daily_issues.name ,daily_issues.description ,daily_issues.type ,daily_issues.owner
Yordan Georgiev
źródło