Dlaczego awk zatrzymuje się i czeka, czy nazwa pliku zawiera = i jak to obejść?

Odpowiedzi:

19

Jak mówi Chris , argumenty formularza variablename=anythingsą traktowane jako przypisanie zmiennej (wykonywane w momencie przetwarzania argumentów, w przeciwieństwie do (nowszych) -v var=value, które są wykonywane przed BEGINinstrukcjami) zamiast nazw plików wejściowych.

Może to być przydatne w takich przypadkach jak:

awk '{print $1}' FS=/ RS='\n' file1 FS='\n' RS= file2

Gdzie możesz określić inny FS/ RSna plik. Jest również powszechnie stosowany w:

awk '!file1_processed{a[$0]; next}; {...}' file1 file1_processed=1 file2

Która jest bezpieczniejszą wersją:

awk 'NR==FNR{a[$0]; next}; {...}' file1 file2

(co nie działa, jeśli file1jest puste)

Ale to przeszkadza, gdy masz pliki, których nazwa zawiera =znaki.

To tylko problem, gdy resztą pierwszego =jest poprawna awknazwa zmiennej.

To, co stanowi prawidłową nazwę zmiennej w, awkjest surowsze niż w sh.

POSIX wymaga czegoś takiego jak:

[_a-zA-Z][_a-zA-Z0-9]*

Tylko znaki z przenośnego zestawu znaków. Jednak /usr/xpg4/bin/awkSolaris 11 przynajmniej nie jest zgodny w tym względzie i dopuszcza wszelkie znaki alfabetyczne w ustawieniach regionalnych w nazwach zmiennych, nie tylko a-zA-Z.

Zatem argument taki jak x+y=foolub =barlub ./foo=barjest nadal traktowany jako nazwa pliku wejściowego, a nie przypisanie, ponieważ to, co pozostało z pierwszego, =nie jest prawidłową nazwą zmiennej. Argument taki Stéphane=Chazelas.txtmoże, ale nie musi, w zależności od awkimplementacji i ustawień regionalnych.

Dlatego w awk zaleca się stosowanie:

awk '...' ./*.txt

zamiast

awk '...' *.txt

na przykład, aby uniknąć problemu, jeśli nie możesz zagwarantować, że nazwa txtplików nie będzie zawierać =znaków.

Pamiętaj też, że taki argument -vfoo=bar.txtmoże być traktowany jako opcja, jeśli używasz:

awk -f file.awk -vfoo=bar.txt

(Dotyczy to również awk '{code}' -vfoo=bar.txtz awkod wersji BusyBox przed 1.28.0, patrz odpowiadający raport o błędzie ).

Ponownie, użycie ./*.txtdziała w ten sposób (użycie ./przedrostka pomaga również w wywołaniu pliku, -który inaczej awkrozumie się jako oznaczający standardowe wejście ).

Właśnie dlatego

#! /usr/bin/awk -f

sekstensy naprawdę nie działają. Podczas gdy var=valuete można obejść poprzez ustalenie tych ARGVwartości (dodać ./przedrostek) w BEGINoświadczeniu:

#! /usr/bin/awk -f
BEGIN {
  for (i = 1; i < ARGC; i++)
    if (ARGV[i] ~ /^[_[:alpha:]][_[:alnum:]]*=/)
      ARGV[i] = "./" ARGV[i]
}
# rest of awk script

To nie pomoże w przypadku opcji, ponieważ są one widziane przez skrypt, awka nie przez niego awk.

Jednym z potencjalnych problemów kosmetycznych z użyciem tego ./prefiksu jest to, że się kończy FILENAME, ale zawsze możesz substr(FILENAME, 3)go rozebrać, jeśli nie chcesz.

Implementacja GNU awknaprawia wszystkie te problemy z jej -Eopcją.

Po tym -Egawk oczekuje tylko ścieżki awkskryptu (gdzie -wciąż oznacza stdin), a następnie tylko listy ścieżek do pliku wejściowego (i tam nawet nie -jest specjalnie traktowany).

Jest specjalnie zaprojektowany dla:

#! /usr/bin/gawk -E

shebangs, w których lista argumentów jest zawsze plikami wejściowymi (pamiętaj, że nadal możesz edytować tę ARGVlistę w BEGINinstrukcji).

Możesz także użyć go jako:

gawk -e '...awk code here...' -E /dev/null *.txt

Używamy -Ez pustym skryptem ( /dev/null), aby upewnić się, że *.txtpóźniej będą zawsze traktowane jako pliki wejściowe, nawet jeśli zawierają =znaki.

Stéphane Chazelas
źródło
Nie widzę problemu z jawną ścieżką kończącą się na FILENAME. Albo skrypt awk jest ogólny, w którym to przypadku powinien obsługiwać wszystkie ścieżki kończące się na FILENAME (w tym, ale nie wyłącznie ../foo, /path/to/fooi ścieżki, które są w innym kodowaniu) - w którym to przypadku substr(FILENAME,3)nie wystarczy, lub skrypt z jednym strzałem, w którym użytkownik w zasadzie zna nazwy plików - w takim przypadku prawdopodobnie nie powinien zawracać sobie głowy żadnym z nich, który zawiera =albo ;-)
mosvy
2
@mosvy Nie wydaje mi się, żeby ./stanowiło to problem, ale może być niepożądane w pewnych warunkach, takich jak przypadki, w których nazwa pliku musi zostać dołączona do wyjścia, w którym to przypadku ./powinna być nadmiarowa i niepotrzebna, więc Muszę się go jakoś pozbyć. Oto co najmniej jeden przykład . Co do użytkownika, który wie, jakie są nazwy plików - cóż, w tym przypadku wiemy również, co to jest nazwa pliku, ale =wciąż przeszkadza w prawidłowym przetwarzaniu. Więc prowadzenie może -przeszkodzić.
Sergiy Kolodyazhnyy
@mosvy, tak, pomysł polega na tym, że chcesz użyć ./przedrostka, aby obejść tę awk(błędną) funkcję, ale potem uzyskasz wynik ./wyjściowy, który możesz chcieć usunąć. Zobacz, jak sprawdzić, czy pierwszy wiersz pliku zawiera określony ciąg? jako przykład.
Stéphane Chazelas,
Jest nie tylko lokalny (w stosunku do tego katalogu), ./ale także globalny (ścieżka bezwzględna), /dzięki czemu awk interpretuje argument jako plik.
Izaak
21

W większości wersji awk argumentami po uruchomieniu programu są:

  1. Plik
  2. Przypisanie formularza x=y

Ponieważ twoja nazwa pliku jest interpretowana jako przypadek nr 2, awk wciąż czeka na coś do odczytania na stdin (ponieważ nie rozpoznaje, że nazwa pliku została przekazana).

Przenośne zachowanie to jest udokumentowane w POSIX :

Można zmieszać jeden z dwóch poniższych typów argumentów:

  • plik: ścieżka do pliku zawierającego dane wejściowe do odczytu, która jest dopasowywana do zestawu wzorców w programie. Jeżeli nie podano żadnych argumentów pliku lub jeśli argumentem pliku jest „-”, należy użyć standardowego wejścia.
  • przypisanie: operand rozpoczynający się znakiem podkreślenia lub alfabetem z przenośnego zestawu znaków (patrz tabela w tomie Definicje podstawowe IEEE Std 1003.1-2001, sekcja 6.1, Przenośny zestaw znaków), po którym następuje sekwencja podkreśleń, cyfr, natomiast alfabet z przenośnego zestawu znaków, po którym następuje znak „=”, określa raczej przypisanie zmiennej niż nazwę ścieżki.

Jako taki, przenośnie, masz kilka opcji (# 1 jest prawdopodobnie najmniej inwazyjny):

  1. Użyj awk ... ./my=file, która omija to, ponieważ .nie jest „znakiem podkreślenia lub alfabetu z przenośnego zestawu znaków”.
  2. Umieść plik na standardowym przy pomocy awk ... < my=file. Nie działa to jednak dobrze z wieloma plikami.
  3. Utwórz tymczasowo dowiązanie do pliku i użyj go. Możesz zrobić coś takiego ln my=file my_file, a następnie użyć my_filejak zwykle. Kopiowanie nie zostanie wykonane, a oba pliki zostaną poparte tymi samymi danymi i metadanymi i-węzłów. Po użyciu można bezpiecznie usunąć utworzone łącze, ponieważ liczba odwołań do i-węzła nadal będzie większa niż 0.
Chris Down
źródło
6
Nie ./my=file działa? % awk 'processing_script_here' ./my=file.txt awk: fatal: cannot open file ./my=file.txt' for reading (No such file or directory). Powinno być przenośne, ponieważ ./mynie jest prawidłową nazwą zmiennej, więc nie powinno być analizowane w ten sposób.
Stephen Harris
2
Jak napisano w tekście POSIX, problem występuje tylko wtedy, gdy pierwszy =jest poprzedzony znakiem podkreślenia lub alfabetem z przenośnego zestawu znaków (patrz tabela w tomie Definicje podstawowe IEEE Std 1003.1-2001, sekcja 6.1, Przenośny zestaw znaków), po którym następuje sekwencja znaków podkreślenia, cyfr i alfabetu z przenośnego zestawu znaków . więc ścieżka do pliku, taka jak ++foo=bar.txtlub =foolub, ./foo=barwszystko jest w porządku jako taka .lub +nie jest [_a-zA-Z].
Stéphane Chazelas,
1
@SergiyKolodyazhnyy awk jest zewnętrzny względem powłoki, więc nie ma znaczenia, którego używasz. ./my=filezostaną przekazane dosłownie.
Chris Down
1
@SergiyKolodyazhnyy, to samo dla awk '{print $1,$2}' /etc/passwd. Chodzi o to, że otwarcie pliku przez powłokę w przeciwieństwie do awk nie ma znaczenia, czy sprawia, że ​​jest on widoczny, czy nie. W rzeczywistości awk '{exit}' < /etc/passwdmożna oczekiwać awkod końca pierwszego rekordu, exitaby upewnić się, że pozostawi on pozycję w obrębie standardowego wejścia. POSIX tego wymaga. /usr/xpg4/bin/awkrobi to na Solarisie, ale ani gawknie mawkwydaje się tego robić na GNU / Linux.
Stéphane Chazelas,
3
@mosvy, patrz sekcja PLIKI WEJŚCIOWE na pubs.opengroup.org/onlinepubs/9699919799/utilities/… Jest przydatny w wielu wzorcach użytkowania, które mają sens tylko w przypadku zwykłych plików, np. gdy chcesz obciąć plik lub zapisać w nim dane pozycja określona w awkten sposób.
Stéphane Chazelas,
3

Aby zacytować dokumentację gawk (uwaga dodana):

Wszelkie dodatkowe argumenty w wierszu poleceń są zwykle traktowane jako pliki wejściowe do przetworzenia w określonej kolejności. Jednak argument, który ma postać var ​​= wartość, przypisuje wartość do zmiennej var - w ogóle nie określa pliku.

Dlaczego polecenie zatrzymuje się i czeka? Ponieważ w formularzu awk 'processing_script_here' my=file.txt nie ma pliku określonego przez powyższą definicję - my=file.txtjest interpretowany jako przypisanie zmiennej, a jeśli nie ma zdefiniowanego pliku awk, odczyta stdin (oczywiste z stracektórego wynika, że ​​awk w takiej komendzie czeka na read(0,'...)syscall.

Jest to również udokumentowane w specyfikacji awk POSIX , patrz sekcja OPERANDS i część przypisań )

Przypisanie zmiennych jest widoczne, awk '{print foo}' foo=bar /etc/passwdponieważ wartość foojest drukowana dla każdej linii w / etc / passwd. Podanie ./foo=barlub pełna ścieżka jednak działa.

Zauważ, że działa straceon awk '1' foo=barjak sprawdzanie z cat foo=barpokazuje, że jest to specyficzny problem awk i execve nie pokazuje nazwę pliku jako argument przekazany, więc pociski nie mają nic wspólnego z przypisań zmiennych env w tej sprawie.

Dodatkowo należy pamiętać, że awk '...script...' foo=barnie spowoduje to tworzenia zmiennych środowiskowych przez powłokę, ponieważ przypisania zmiennych środowiskowych powinny poprzedzać polecenie, aby zadziałały. Patrz Zasady gramatyki powłoki POSIX , punkt nr 7. Dodatkowo można to sprawdzić za pomocąawk '{print ENVIRON["foo"]}' foo=bar /etc/passwd

Sergiy Kolodyazhnyy
źródło