Tworzę plik z polami rozdzielanymi tabulatorami.
echo foo$'\t'bar$'\t'baz$'\n'foo$'\t'bar$'\t'baz > input
Mam następujący skrypt o nazwie zsh.sh
#!/usr/bin/env zsh
while read line; do
<<<$line cut -f 2
done < "$1"
Testuję to.
$ ./zsh.sh input
bar
bar
To działa dobrze. Jednak gdy zmienię pierwszy wiersz, aby bash
zamiast tego wywoływał , nie powiedzie się.
$ ./bash.sh input
foo bar baz
foo bar baz
Dlaczego to nie bash
działa i działa z zsh
?
Dodatkowe rozwiązywanie problemów
- Użycie bezpośrednich ścieżek w shebang zamiast
env
powoduje takie samo zachowanie. - Pipingowanie z
echo
zamiast ciągów tutaj<<<$line
również powoduje takie samo zachowanie. tjecho $line | cut -f 2
. - Używanie
awk
zamiastcut
działa dla obu powłok. tj<<<$line awk '{print $2}'
.
bash
zsh
quoting
whitespace
here-string
Krogulec
źródło
źródło
echo -e 'foo\tbar\tbaz\n...'
,echo $'foo\tbar\tbaz\n...'
lubprintf 'foo\tbar\tbaz\n...\n'
lub odmiany tych. Pozwala to uniknąć konieczności indywidualnego zawijania każdej karty lub nowej linii.Odpowiedzi:
To, co się dzieje,
bash
zastępuje tabulatory spacjami. Możesz uniknąć tego problemu, mówiąc w"$line"
zamian lub jawnie ograniczając spacje.źródło
\t
i zamienia go na spację?<<< $line
,bash
dzieli, ale nie glob. Nie ma powodu, dla którego podzieliłby się tutaj, jak<<<
oczekuje się jednego słowa. W tym przypadku dzieli się, a następnie łączy, co nie ma większego sensu i jest sprzeczne ze wszystkimi innymi implementacjami powłok, które były obsługiwane<<<
wcześniej lub późniejbash
. IMO to błąd.Wynika to z faktu, że in
<<< $line
,bash
dzielenie wyrazów (choć nie globowanie) jest włączone,$line
ponieważ nie jest tam cytowane, a następnie łączy wynikowe słowa ze znakiem spacji (i umieszcza to w pliku tymczasowym, po którym następuje znak nowej linii i robi to od początkucut
).tab
zdarza się, że ma domyślną wartość$IFS
:Rozwiązaniem
bash
jest zacytowanie zmiennej.Zauważ, że jest to jedyna powłoka, która to robi.
zsh
(skąd<<<
pochodzi, zainspirowany uniksowym portemrc
)ksh93
,mksh
ayash
które także wsparcie<<<
tego nie robią.Kiedy przychodzi do tablic
mksh
,yash
izsh
dołącz na pierwszy znak$IFS
,bash
aksh93
na przestrzeni.Istnieje różnica między
zsh
/yash
imksh
(przynajmniej wersją R52), gdy$IFS
jest pusta:Zachowanie jest bardziej spójne podczas używania powłok
"${a[*]}"
(z wyjątkiem tego, żemksh
nadal występuje błąd, gdy$IFS
jest pusty).W
echo $line | ...
, to zwykły operator podzielonego + glob we wszystkich Bourne'a jak muszle, alezsh
(i zwykłych problemów związanych zecho
).źródło
Problem polega na tym, że nie cytujesz
$line
. Aby to sprawdzić, zmień dwa skrypty, aby po prostu wydrukowały$line
:i
Teraz porównaj ich wyniki:
Jak widać, ponieważ nie cytujesz
$line
, zakładki nie są poprawnie interpretowane przez bash. Zsh wydaje się, że radzi sobie z tym lepiej. Teraz domyślniecut
używa\t
jako ogranicznika pola. Dlatego, ponieważ twójbash
skrypt zjada karty (z powodu operatora split + glob),cut
widzi tylko jedno pole i działa odpowiednio. To, co naprawdę biegasz, to:Tak więc, aby skrypt działał zgodnie z oczekiwaniami w obu powłokach, podaj swoją zmienną:
Następnie oba generują tę samą wydajność:
źródło
bash.sh
Jak już wspomniano, bardziej przenośnym sposobem użycia zmiennej jest zacytowanie jej:
Istnieje różnica w implementacji w bash, z linią:
Jest to wynik większości powłok:
Tylko bash dzieli zmienną po prawej stronie,
<<<
gdy nie jest cytowany.Zostało to jednak poprawione w wersji bash 4.4.
Oznacza to, że wartość parametru
$IFS
wpływa na wynik<<<
.Z linią:
Wszystkie powłoki używają pierwszego znaku IFS do łączenia wartości.
W
"${l[@]}"
przypadku oddzielenia różnych argumentów potrzebna jest spacja, ale niektóre powłoki wybierają wartość z IFS (czy to prawda?).Przy zerowym IFS wartości powinny zostać połączone, podobnie jak w tym wierszu:
Ale zarówno lksh, jak i mksh tego nie robią.
Jeśli przejdziemy do listy argumentów:
Zarówno yash, jak i zsh nie rozdzielają argumentów. Czy to błąd?
źródło
zsh
/yash
i"${l[@]}"
w kontekście innym niż lista, to zgodnie z projektem, gdzie"${l[@]}"
jest wyjątkowy tylko w kontekście listy. W kontekstach nie listowych nie ma możliwości separacji , musisz jakoś połączyć elementy. Łączenie z pierwszym znakiem $ IFS jest bardziej spójne niż łączenie ze znakiem spacji IMO.dash
robi to również (dash -c 'IFS=; a=$@; echo "$a"' x a b
). Jednak POSIX zamierza zmienić ten IIRC. Zobacz tę (długą) dyskusjęvar=$@
nieokreślone.