Czym przekierowanie pliku bash do standardu różni się od powłoki (`sh`) w systemie Linux?

9

Napisałem skrypt, który przełącza użytkowników podczas działania, i wykonałem go za pomocą przekierowania plików do standardowego wejścia. Tak więc user-switch.sh...

#!/bin/bash

whoami
sudo su -l root
whoami

I uruchomienie go bashdaje oczekiwane zachowanie

$ bash < user-switch.sh
vagrant
root

Jeśli jednak uruchomię skrypt sh, otrzymam inne dane wyjściowe

$ sh < user-switch.sh 
vagrant
vagrant

Dlaczego bash < user-switch.shdaje inną wydajność niż sh < user-switch.sh?

Uwagi:

  • dzieje się na dwóch różnych urządzeniach z systemem Debian Jessie
popedotninja
źródło
1
Nie odpowiedź, ale dotycząca sudo su: unix.stackexchange.com/questions/218169/…
Kusalananda
1
jest / bin / sh = dash na Debianie? Nie mogę repro w RHEL gdzie / bin / sh = bash
Jeff Schaller
1
/bin/shna (mojej kopii) Debianie to Dash, tak. Do tej pory nawet nie wiedziałem, co to jest. Do tej pory tkwiłem w bashu.
popedotninja
1
Nie jest również gwarantowane, że zachowanie bash będzie działało według jakiejkolwiek udokumentowanej semantyki.
Charles Duffy
Ktoś mi dzisiaj wskazał, że skrypt, który uruchomiłem, naruszył semantykę tego, jak powinien być używany bash. Było kilka świetnych odpowiedzi na to pytanie, ale warto zauważyć, że prawdopodobnie nie powinno się przełączać użytkowników w połowie skryptu. Początkowo próbowałem user-switch.shw pracy Jenkinsa i skrypt został wykonany. Uruchomienie tego samego skryptu poza Jenkins przyniosło różne wyniki i skorzystałem z przekierowania pliku, aby uzyskać zachowanie, które widziałem w Jenkins.
popedotninja

Odpowiedzi:

12

Podobny skrypt bez sudo, ale podobne wyniki:

$ cat script.sh
#!/bin/bash
sed -e 's/^/--/'
whoami

$ bash < script.sh
--whoami

$ dash < script.sh
itvirta

Z bash, reszta skryptu idzie jako wejście do sed, z dash, na interpretuje ją powłoki.

Uruchamianie stracena tych: dashczyta blok skryptu (tutaj osiem kB, więcej niż wystarcza do utrzymania całego skryptu), a następnie odradza się sed:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 8192) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Co oznacza, że ​​uchwyt pliku znajduje się na końcu pliku i sednie będzie widział żadnych danych wejściowych. Pozostała część jest buforowana wewnątrz dash. (Jeśli skrypt był dłuższy niż rozmiar bloku 8 kB, pozostała część zostałaby odczytana sed).

Bash natomiast stara się wrócić do końca ostatniego polecenia:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 36) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
...
lseek(0, -7, SEEK_CUR)                  = 29
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Jeśli dane wejściowe pochodzą z potoku, tak jak tutaj:

$ cat script.sh | bash

przewijanie nie jest możliwe, ponieważ rury i gniazda nie są widoczne. W takim przypadku Bash wraca do czytania jednego znaku na raz, aby uniknąć czytania. ( fd_to_buffered_stream()ininput.c ) Wykonanie pełnego wywołania systemowego dla każdego bajtu nie jest w zasadzie bardzo skuteczne. W praktyce nie sądzę, aby odczyty były dużym narzutem w porównaniu np. Z faktem, że większość rzeczy, które wykonuje powłoka, obejmuje tworzenie nowych procesów.

Podobna sytuacja jest taka:

echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1'

Podpowłoka musi się upewnić, że readczyta tylko pierwszą headnową linię , aby zobaczyć następną linię. (To też działa dash.)


Innymi słowy, Bash dokłada wszelkich starań, aby obsługiwać czytanie tego samego źródła dla samego skryptu i poleceń z niego wykonywanych. dashnie. zshI ksh93pakowany w Debianie iść z Bash w tej sprawie.

ilkkachu
źródło
1
Szczerze mówiąc, jestem trochę zaskoczony, że to działa.
ilkkachu
dzięki za świetną odpowiedź! Uruchomię oba polecenia przy użyciu stracepóźniej i porównuję wyniki. Nie pomyślałem o takim podejściu.
popedotninja
2
Tak, moja reakcja po przeczytaniu pytanie było zaskoczeniem, że to czyni pracę z bash - Miałem to złożony z dala jako coś, co na pewno nie będzie działać. Chyba miałem tylko połowę racji :)
Hobbs
12

Powłoka odczytuje skrypt ze standardowego wejścia. Wewnątrz skryptu uruchamiasz polecenie, które chce również odczytać standardowe dane wejściowe. Który wkład pójdzie gdzie? Nie możesz rzetelnie powiedzieć .

Powłoki działają tak, że odczytują fragment kodu źródłowego, analizują go, a jeśli znajdą kompletne polecenie, uruchom je, a następnie kontynuuj z pozostałą częścią fragmentu i pozostałą częścią pliku. Jeśli fragment nie zawiera kompletnego polecenia (ze znakiem kończącym na końcu - myślę, że wszystkie powłoki czytają do końca linii), powłoka odczytuje inny fragment i tak dalej.

Jeśli polecenie w skrypcie próbuje odczytać z tego samego deskryptora pliku, z którego powłoka czyta skrypt, to polecenie znajdzie cokolwiek, co nastąpi po ostatnim fragmencie, który przeczytał. Ta lokalizacja jest nieprzewidywalna: zależy od wielkości porcji, którą wybrała powłoka, i może zależeć nie tylko od powłoki i jej wersji, ale także od konfiguracji maszyny, dostępnej pamięci itp.

Bash szuka do końca kodu źródłowego polecenia w skrypcie przed wykonaniem polecenia. Nie jest to coś, na co możesz liczyć, nie tylko dlatego, że inne powłoki tego nie robią, ale także dlatego, że działa to tylko wtedy, gdy powłoka czyta ze zwykłego pliku. Jeśli powłoka odczytuje z potoku (np. ssh remote-host.example.com <local-script-file.sh), Odczytane dane są odczytywane i nie można ich odczytać.

Jeśli chcesz przekazać dane wejściowe do polecenia w skrypcie, musisz to zrobić jawnie, zwykle w dokumencie tutaj . (Dokument tutaj jest zwykle najwygodniejszy dla wprowadzania wieloliniowego, ale wystarczy dowolna metoda.) Napisany kod działa tylko w kilku powłokach, tylko jeśli skrypt jest przekazywany jako dane wejściowe do powłoki ze zwykłego pliku; jeśli spodziewałeś się, że sekunda whoamizostanie przekazana jako dane wejściowe do sudo …, pomyśl jeszcze raz, pamiętając, że przez większość czasu skrypt nie jest przekazywany do standardowego wejścia powłoki.

#!/bin/bash
whoami
sudo su -l root <<'EOF'
whoami
EOF

Pamiętaj, że w tej dekadzie możesz używać sudo -i root. Bieganie sudo suto hack z przeszłości.

Gilles „SO- przestań być zły”
źródło