Oto seria przypadków, w których echo $var
można pokazać inną wartość niż ta, która została właśnie przypisana. Dzieje się tak niezależnie od tego, czy przypisana wartość była „w cudzysłowie”, „w pojedynczym cudzysłowie”, czy nie.
Jak sprawić, by powłoka poprawnie ustawiła moją zmienną?
Gwiazdki
Oczekiwany wynik to /* Foobar is free software */
, ale zamiast tego otrzymuję listę nazw plików:
$ var="/* Foobar is free software */"
$ echo $var
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...
Nawiasy kwadratowe
Oczekiwana wartość to [a-z]
, ale czasami zamiast tego otrzymuję pojedynczą literę!
$ var=[a-z]
$ echo $var
c
Kanały (nowe linie)
Oczekiwana wartość to lista oddzielnych linii, ale zamiast tego wszystkie wartości znajdują się w jednym wierszu!
$ cat file
foo
bar
baz
$ var=$(cat file)
$ echo $var
foo bar baz
Wiele przestrzeni
Spodziewałem się starannie wyrównanego nagłówka tabeli, ale zamiast tego wiele spacji znika lub jest zwiniętych w jedną!
$ var=" title | count"
$ echo $var
title | count
Tabs
Spodziewałem się dwóch wartości rozdzielonych tabulatorami, ale zamiast tego otrzymuję dwie wartości oddzielone spacjami!
$ var=$'key\tvalue'
$ echo $var
key value
var=$(cat file)
jest dobrze, aleecho "$var"
jest potrzebne.Odpowiedzi:
We wszystkich powyższych przypadkach zmienna jest poprawnie ustawiona, ale niepoprawnie odczytana! Właściwym sposobem jest użycie podwójnych cudzysłowów przy odwoływaniu się do :
Daje to oczekiwaną wartość we wszystkich podanych przykładach. Zawsze cytuj odniesienia do zmiennych!
Czemu?
Gdy zmienna nie jest cytowana , będzie:
Poddaj się podziałowi na pola, w którym wartość jest dzielona na wiele słów w odstępach (domyślnie):
Przed:
/* Foobar is free software */
Po:
/*
,Foobar
,is
,free
,software
,*/
Każde z tych słów zostanie rozwinięte , a wzorce zostaną rozwinięte do pasujących plików:
Przed:
/*
Po:
/bin
,/boot
,/dev
,/etc
,/home
, ...Na koniec wszystkie argumenty są przekazywane do echa, które wypisuje je oddzielone pojedynczymi odstępami , dając
zamiast wartości zmiennej.
Kiedy zmienna jest cytowana, to:
Dlatego zawsze należy cytować wszystkie odwołania do zmiennych , chyba że wyraźnie wymaga się dzielenia na słowa i rozwijania nazw plików. Narzędzia takie jak shellcheck służą pomocą i ostrzegają o brakujących cudzysłowach we wszystkich powyższych przypadkach.
źródło
$(..)
paski końcowe linii wysuwów. Możeszvar=$(cat file; printf x); var="${var%x}"
go obejść.Możesz chcieć wiedzieć, dlaczego tak się dzieje. Razem ze świetnym wyjaśnieniem tego innego gościa znajdź odniesienie do Dlaczego mój skrypt powłoki dławi się białymi znakami lub innymi znakami specjalnymi? napisane przez Gillesa w systemach Unix i Linux :
źródło
Oprócz innych problemów spowodowanych brakiem cytowania
-n
i-e
może być używanyecho
jako argumenty. (Tylko ta pierwsza jest legalna zgodnie ze specyfikacją POSIXecho
, ale kilka typowych implementacji narusza specyfikację i także konsumuje-e
).Aby tego uniknąć, użyj
printf
zamiastecho
kiedy szczegóły mają znaczenie.A zatem:
Jednak prawidłowe cytowanie nie zawsze uratuje Cię podczas korzystania z
echo
:... podczas gdy to będzie cię uratować z
printf
:źródło
-e
/-n
/ nie jest wyświetlany?” W razie potrzeby możemy dodać linki z tego miejsca.-n
jak również ?-e
. Standard forecho
nie określa wyjścia, gdy jego pierwszy argument ma wartość-n
, czyniąc jakiekolwiek / wszystkie możliwe wyjście legalnym w tym przypadku; nie ma takiego przepisu-e
.podwójny cudzysłów użytkownika, aby uzyskać dokładną wartość. lubię to:
i poprawnie odczyta twoją wartość.
źródło
echo $var
wyjście w dużym stopniu zależy od wartościIFS
zmiennej. Domyślnie zawiera spacje, tabulatory i znaki nowej linii:Oznacza to, że gdy powłoka dokonuje podziału na pola (lub na słowa), używa wszystkich tych znaków jako separatorów słów. Dzieje się tak, gdy odwołujemy się do zmiennej bez podwójnych cudzysłowów, aby ją powtórzyć (
$var
), a zatem oczekiwane dane wyjściowe są zmieniane.Jednym ze sposobów zapobiegania dzieleniu słów (oprócz stosowania podwójnych cudzysłowów) jest ustawienie
IFS
wartości null. Zobacz http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 :Ustawienie na null oznacza ustawienie na pustą wartość:
Test:
źródło
set -f
zapobiec globbingowiIFS
ustawienia wartości nullecho $var
zostanie rozwinięty do,echo '/* Foobar is free software */'
a rozwinięcie ścieżki nie jest wykonywane wewnątrz ciągów w pojedynczych cudzysłowach.mkdir "/this thing called Foobar is free software etc/"
zobaczysz, że nadal się rozszerza. Na przykład jest to oczywiście bardziej praktyczne[a-z]
.[a-z]
przykład ma to sens .Odpowiedź od ks1322 pomógł mi zidentyfikować ten problem podczas korzystania z
docker-compose exec
:Jeśli pominiesz
-T
flagę,docker-compose exec
dodaj znak specjalny, który przerywa wyjście,b
zamiast1b
:Z
-T
flagądocker-compose exec
działa zgodnie z oczekiwaniami:źródło
Oprócz umieszczenia zmiennej w cudzysłowie, można również przetłumaczyć wynik zmiennej za pomocą
tr
i zamieniając spacje na znaki nowej linii.Chociaż jest to trochę bardziej zawiłe, dodaje więcej różnorodności do danych wyjściowych, ponieważ można zastąpić dowolny znak jako separator między zmiennymi tablicowymi.
źródło
tr
odwrotnego sposobu tworzenia tablic z plików tekstowych.tr
potrzeby prawidłowego / poprawnego tworzenia tablicy z pliku tekstowego - możesz określić dowolny separator, ustawiając IFS. Na przykład:IFS=$'\n' read -r -d '' -a arrayname < <(cat file.txt && printf '\0')
działa przez całą drogę wstecz do bash 3.2 (najstarsza wersja w szerokim obiegu) i poprawnie ustawia status wyjścia na false, jeśli twój błąd sięcat
nie powiódł. A jeśli chciał, powiedzmy, zakładki zamiast znaki nowej linii, to że po prostu wymienić$'\n'
się$'\t'
.arrayname=( $( cat file | tr '\n' ' ' ) )
, to jest to zepsute na wielu warstwach: globalizuje twoje wyniki (więc*
zmienia się w listę plików w bieżącym katalogu) i działałoby równie dobrze beztr
( lubcat
, jeśli o to chodzi; można by po prostu użyćarrayname=$( $(<file) )
i zostałoby złamane w ten sam sposób, ale mniej nieefektywnie).