Chcę być w stanie uchwycić dokładny wynik podstawienia polecenia, w tym końcowe znaki nowego wiersza .
Zdaję sobie sprawę, że są one domyślnie usuwane, więc może być wymagana pewna manipulacja, aby je zachować, i chcę zachować oryginalny kod wyjścia .
Na przykład, biorąc pod uwagę polecenie ze zmienną liczbą końcowych znaków nowej linii i kodem wyjścia:
f(){ for i in $(seq "$((RANDOM % 3))"); do echo; done; return $((RANDOM % 256));}
export -f f
Chcę uruchomić coś takiego:
exact_output f
I niech wynik będzie:
Output: $'\n\n'
Exit: 5
Interesuje mnie zarówno bash
POSIX sh
.
$IFS
, więc nie zostanie przechwycony jako argument.IFS
(spróbuj( IFS=:; subst=$(printf 'x\n\n\n'); printf '%s' "$subst" )
. Tylko nowe linie są usuwane.\t
I `` nie, iIFS
nie ma na to wpływu.tcsh
Odpowiedzi:
Pociski POSIX
Zwykłą ( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ) sztuczką, aby uzyskać kompletny krok polecenia, jest:
Chodzi o to, aby dodać i dodatkowo
.\n
. Zastąpienie polecenia spowoduje tylko usunięcie tego\n
. I rozebrać.
z${output%.}
.Zauważ, że w powłokach innych niż
zsh
, to nadal nie będzie działać, jeśli wyjście ma NUL bajtów. Dziękiyash
nie będzie to działać, jeśli wynik nie jest tekstem.Pamiętaj też, że w niektórych lokalizacjach ma znaczenie to, jakiego znaku użyjesz na końcu.
.
ogólnie powinno być w porządku, ale niektóre inne mogą nie. Na przykładx
(jak w niektórych innych odpowiedziach) lub@
nie działałby w ustawieniach regionalnych przy użyciu zestawów znaków BIG5, GB18030 lub BIG5HKSCS. W tych zestawach znaków kodowanie wielu znaków kończy się tym samym bajtem, co kodowaniex
lub@
(0x78, 0x40)Na przykład
ū
w BIG5HKSCS wynosi 0x88 0x78 (ix
0x78 jak w ASCII, wszystkie zestawy znaków w systemie muszą mieć to samo kodowanie dla wszystkich znaków przenośnego zestawu znaków zawierającego litery angielskie@
i.
). Gdyby takcmd
byłoprintf '\x88'
i wstawiliśmyx
po nim,${output%x}
nie usunęłoby tego,x
co$output
faktycznie zawierałobyū
.Używanie
.
zamiast tego może prowadzić do tego samego problemu teoretycznie, jeśli byłyby jakieś znaki, których kodowanie kończy się takim samym kodowaniem jak.
, ale po sprawdzeniu jakiś czas temu mogę powiedzieć, że żaden z zestawów znaków, które mogą być dostępne do użycia w ustawieniach regionalnych w Systemy Debian, FreeBSD lub Solaris mają takie znaki, które są dla mnie wystarczająco dobre (i dlaczego zdecydowałem się na.
to, że jest to również symbol oznaczający koniec zdania w języku angielskim, więc wydaje się to właściwe).Bardziej poprawnym podejściem omawianym przez @Arrow byłaby zmiana ustawień regionalnych na C tylko dla usuwania ostatniego znaku (
${output%.}
), co zapewniłoby usunięcie tylko jednego bajtu, ale to znacznie skomplikowałoby kod i potencjalnie wprowadziłoby problemy z kompatybilnością jego.alternatywy bash / zsh
Za pomocą
bash
izsh
, zakładając, że dane wyjściowe nie mają wartości NUL, możesz także:Aby uzyskać status wyjścia
cmd
, możesz zrobićwait "$!"; ret=$?
w,bash
ale nie wzsh
.rc / es / akanaga
Dla kompletności zwróć uwagę, że
rc
/es
/akanga
mają do tego operatora. W nich podstawienie polecenia wyrażone jako`cmd
(lub w`{cmd}
przypadku bardziej złożonych poleceń) zwraca listę ($ifs
domyślnie dzieląc klawisz spacja-tab-nowa linia). W tych powłokach (w przeciwieństwie do powłok podobnych do Bourne'a) usuwanie nowej linii odbywa się tylko w ramach tego$ifs
podziału. Możesz więc opróżnić$ifs
lub użyć``(seps){cmd}
formularza, w którym określasz separatory:lub:
W każdym razie status wyjścia polecenia zostanie utracony. Będziesz musiał osadzić go w wyjściu i wyodrębnić później, co stałoby się brzydkie.
ryba
W przypadku ryb zastępowanie poleceń odbywa się za
(cmd)
pomocą podpowłoki.Tworzy
$var
tablicę ze wszystkimi wierszami na wyjściucmd
if$IFS
jest niepustym lub z wynikiemcmd
pozbawionym do jednego (w przeciwieństwie do wszystkich w większości innych powłok) znaku nowej linii, jeśli$IFS
jest pusty.Więc wciąż jest w tym problem
(printf 'a\nb')
i(printf 'a\nb\n')
rozwinąć się do tego samego nawet z pustym$IFS
.Aby obejść ten problem, najlepiej wymyślić:
Alternatywą jest wykonanie:
Skorupa Bourne'a
Powłoka Bourne'a nie obsługiwała
$(...)
ani formy, ani${var%pattern}
operatora, więc może być tam dość trudno go osiągnąć. Jednym z podejść jest użycie eval i cytowania:Tutaj generujemy
zostać przekazanym
eval
. Jeśli chodzi o podejście POSIX, jeśli'
byłby to jeden z tych znaków, którego kodowanie można znaleźć na końcu innych znaków, mielibyśmy problem (znacznie gorszy, ponieważ stałby się podatny na iniekcję poleceń), ale na szczęście, jak.
: to nie jest jedna z nich, a ta technika cytowania jest generalnie stosowana przez wszystko, co cytuje kod powłoki (uwaga, że\
ma problem, więc nie powinna być używana (wyklucza również"..."
wewnątrz, w których trzeba używać odwrotnych ukośników dla niektórych znaków) Tutaj używamy go tylko po tym,'
co jest w porządku).tcsh
Zobacz tcsh zachowaj znaki nowej linii w podstawianiu poleceń `...`
(nie dbając o status wyjścia, który można rozwiązać, zapisując go w pliku tymczasowym (
echo $status > $tempfile:q
po poleceniu))źródło
zsh
można przechowywaćNUL
w zmiennej, dlaczego nie miałbyIFS= read -rd '' output < <(cmd)
działać? Musi mieć możliwość przechowywania długości ciągu ... czy koduje go''
jako ciąg 1-bajtowy,\0
a nie ciąg 0-bajtowy?read -d ''
jest traktowany jakoread -d $'\0'
(wbash
tym, że$'\0'
jest tak samo jak''
wszędzie).x
jeśli tak zostało dodane, nie jest trudne . Proszę spojrzeć na moją zredagowaną odpowiedź.var=value command eval
sztuczka była omawiana tutaj ( także ) i na liście mailingowej grupy austin wcześniej. Przekonasz się, że nie jest przenośny (i jest całkiem oczywiste, gdy próbujesz czegoś takiegoa=1 command eval 'unset a; a=2'
lub gorzej, że nie miał być tak używany). To samo dotyczy tego,savedVAR=$VAR;...;VAR=$savedVAR
że nie robi tego, co chcesz, gdy$VAR
początkowo był rozbrojony. Jeśli ma to obejść tylko problem teoretyczny (błąd, którego nie można w praktyce trafić), IMO, nie warto się tym przejmować. Mimo to będę cię wspierać za próbę.LANG=C
do usunięcia bajtu z ciągu? Podnosisz obawy wokół rzeczywistego punktu, wszystkie są łatwe do rozwiązania. (1) nie zastosowano nieuzbrojonego (2) Przetestuj zmienną przed jej zmianą. @ StéphaneChazelasW przypadku nowego pytania skrypt działa:
Po wykonaniu:
Dłuższy opis
Zwykłą mądrością dla powłok POSIX do radzenia sobie z usuwaniem
\n
jest:Jest to wymagane, ponieważ ostatnia nowa linia ( S ) jest usuwana przez rozszerzenie komend zgodnie ze specyfikacją POSIX :
O wleczeniu
x
.W tym pytaniu powiedziano, że
x
można pomylić z końcowym bajtem jakiegoś znaku w niektórych kodowaniach. Ale jak zgadniemy, która lub która postać jest lepsza w jakimś języku w jakimś możliwym kodowaniu, co jest co najmniej trudną propozycją.Jednak; To jest po prostu nieprawidłowe .
Jedyną zasadą, którą musimy przestrzegać, jest dodawanie dokładnie tego , co usuwamy.
Powinno być łatwe do zrozumienia, że jeśli dodamy coś do istniejącego ciągu (lub sekwencji bajtów), a później usuniemy dokładnie to samo, oryginalny ciąg (lub sekwencja bajtów) musi być taki sam.
Gdzie popełniamy błąd? Kiedy mieszamy znaki i bajty .
Jeśli dodamy bajt, musimy usunąć bajt, jeśli dodamy znak, musimy usunąć dokładnie ten sam znak .
Druga opcja, dodawanie znaku (a później usunięcie dokładnie tego samego znaku) może stać się skomplikowane i złożone, i tak, strony kodowe i kodowanie mogą przeszkadzać.
Jednak pierwsza opcja jest całkiem możliwa, a po jej wyjaśnieniu stanie się po prostu prosta.
Dodajmy bajt, bajt ASCII (<127), i aby zachować jak najmniej skomplikowaną sytuację, powiedzmy znak ASCII w zakresie az. Albo jak należy go mówiąc, bajt w zakresie hex
0x61
-0x7a
. Wybierzmy dowolny z nich, może x (naprawdę bajt wartości0x78
). Możemy dodać taki bajt, łącząc x z ciągiem (załóżmy, żeé
):Jeśli spojrzymy na ciąg jako sekwencję bajtów, zobaczymy:
Ciąg znaków kończący się na x.
Jeśli usuniemy ten x (wartość bajtu
0x78
), otrzymamy:Działa bez problemu.
Trochę trudniejszy przykład.
Powiedzmy, że ciąg, który nas interesuje, kończy się bajtem
0xc3
:I dodajmy bajt wartości
0xa9
Ciąg stał się teraz taki:
Dokładnie to, czego chciałem, ostatnie dwa bajty to jeden znak w utf8 (aby każdy mógł odtworzyć te wyniki w swojej konsoli utf8).
Jeśli usuniemy znak, oryginalny ciąg zostanie zmieniony. Ale to nie to, co dodaliśmy, dodaliśmy wartość bajtu, która przypadkowo jest zapisywana jako x, ale bajt i tak.
Czego potrzebujemy, aby uniknąć błędnej interpretacji bajtów jako znaków. Potrzebujemy działania, które usuwa użyty bajt
0xa9
. W rzeczywistości ash, bash, lksh i mksh wydają się robić dokładnie to:Ale nie ksh ani zsh.
Jest to jednak bardzo łatwe do rozwiązania, powiedzmy wszystkim tym powłokom, aby usunęły bajty:
to wszystko, wszystkie testowane powłoki działają (oprócz yash) (dla ostatniej części łańcucha):
Po prostu powiedz powłoce, aby usunęła znak LC_ALL = C, który jest dokładnie jednym bajtem dla wszystkich wartości bajtów od
0x00
do0xff
.Rozwiązanie dla komentarzy:
Dla przykładu omówionego w komentarzach jednym z możliwych rozwiązań (które nie działa w Zsh) jest:
To usunie problem z kodowaniem.
źródło
zsh
dodano wprintf -v
celu zapewnienia zgodności zbash
grudniem 2015 r.${var%?}
zawsze usunie jeden bajt, jest bardziej poprawne teoretycznie, ale: 1LC_ALL
iLC_CTYPE
zastąp$LANG
, więc musisz ustawićLC_ALL=C
2, nie możesz zrobićvar=${var%?}
w podpowłoce, ponieważ zmiana zgubić się, więc trzeba zapisać i przywrócić wartość i stanLC_ALL
(lub uciekać się do funkcji spozalocal
zakresu POSIX ) 3- zmiana ustawień regionalnych w połowie skryptu nie jest w pełni obsługiwana w niektórych powłokach, takich jak yash. Z drugiej strony w praktyce.
nigdy nie stanowi problemu w prawdziwych zestawach znaków, więc używanie go pozwala uniknąć mieszania się z LC_ALL.Możesz wypisać znak po normalnym wyjściu, a następnie usunąć go:
Jest to rozwiązanie zgodne z POSIX.
źródło