Jak mogę rozwinąć cytowaną zmienną do zera, jeśli jest pusta?

21

Powiedzmy, że mam skrypt wykonujący:

some-command "$var1" "$var2" ...

A w przypadku var1pustego wolałbym, aby zamiast pustego łańcucha zastąpić go niczym, aby polecenie zostało wykonane:

some-command "$var2" ...

i nie:

some-command '' "$var2" ...

Czy istnieje prostszy sposób niż testowanie zmiennej i warunkowe włączenie jej?

if [ -n "$1" ]; then
    some-command "$var1" "$var2" ...
    # or some variant using arrays to build the command
    # args+=("$var1")
else
    some-command "$var2" ...
fi

Czy istnieje podstawienie parametru, które nie może rozwinąć się w nic w bash, zsh itp.? Nadal mógłbym chcieć użyć globowania w pozostałych argumentach, więc wyłączenie tego i cofnięcie cytowania zmiennej nie jest opcją.

muru
źródło
Wiedziałem, że już to widziałem i prawdopodobnie tego używałem, ale przeszukanie okazało się trudne. Teraz, gdy Michael pokazał składnię, przypomniałem sobie, gdzie zobaczyłem ją wystarczająco szybko: unix.stackexchange.com/a/269549/70524 , unix.stackexchange.com/q/68484/70524
muru
Jeśli wiesz, że jest to pewnego rodzaju zamiana parametrów, dlaczego nie zajrzałeś do sekcji rozwijania parametrówman strony? (-;
Philippos
1
@Filippos Nie wiedziałem, co to było w tamtym czasie, tyle że wcześniej go widziałem lub używałem. Znane znane i nieznane znane. :(
muru
1
dodatkowe punkty cookie za wzmiankę o użyciu tablicy do przechowywania argumentów w samym pytaniu.
ilkkachu

Odpowiedzi:

29

Pociski i Bash zgodne z Posix${parameter:+word} :

Jeśli parametr jest nieustawiony lub ma wartość NULL, wartość NULL należy zastąpić; w przeciwnym razie rozwinięcie słowa (lub pusty ciąg znaków, jeśli słowo zostanie pominięte) zostanie zastąpione.

Możesz więc po prostu zrobić:

${var1:+"$var1"}

i zostały var1sprawdzone i "$var1"użyte, jeśli jest ustawione i niepuste (ze zwykłymi regułami podwójnego cytowania). W przeciwnym razie rozwija się do zera. Zauważ, że cytowana jest tylko część wewnętrzna , a nie całość.

To samo działa również w Zsh. Musisz powtórzyć zmienną, więc nie jest idealna, ale działa dokładnie tak, jak chcesz.

Jeśli chcesz, aby zmienna set-but-empty rozwijała się do pustego argumentu, użyj ${var1+"$var1"}zamiast tego.

Michael Homer
źródło
1
Wystarczająco dobrze dla mnie. Tak więc w tym nie wszystko jest cytowane, tylko wordczęść.
muru
1
Kiedy zadałem pytanie „bash, zsh lub tym podobne” (i dla archiwum pytań i odpowiedzi), zredagowałem, aby odzwierciedlić, że jest to funkcja posix. Nawet jeśli dodasz tag / bash do pytania po moim komentarzu. (-;
Philippos
2
Pozwoliłem sobie na edycję w różnicy między :+i +.
ilkkachu
Wielkie dzięki za rozwiązanie zgodne z POSIX! Staram się używać sh/ dashdla potencjalnych skryptów chokepoint, więc zawsze doceniam to, gdy ktoś pokazuje, jak coś jest możliwe bez uciekania się do Basha.
JamesTheAwesomeDude
Szukałem odwrotnego efektu (rozwinąć do czegoś innego, jeśli zaczyna się od zera). Okazuje się, że tę logikę można odwrócić, zmieniając znak + na - jak w: $ {empty_var: -replacement}
Alex Jansen
5

Tak zshdzieje się domyślnie po pominięciu cytatów:

some-command $var1 $var2

W rzeczywistości jedynym powodem, dla którego nadal potrzebujesz cudzysłowów w rozszerzeniu parametru zsh, jest uniknięcie takiego zachowania (puste usuwanie), ponieważ zshnie ma innych problemów, które wpływają na inne powłoki, gdy nie podajesz rozszerzeń parametrów (domyślny podział + glob) .

Możesz zrobić to samo z innymi powłokami podobnymi do POSIX, jeśli wyłączysz split i glob:

(IFS=; set -o noglob; some-command $var1 $var2)

Teraz twierdzę, że jeśli twoja zmienna może mieć wartość 0 lub 1, powinna być tablicą, a nie zmienną skalarną i użyj:

some-command "${var1[@]}" "${var2[@]}"

I użyj var1=(value)kiedy var1ma zawierać jedną wartość, var1=('')kiedy ma zawierać jedną pustą wartość, a var1=()kiedy nie zawiera żadnej wartości.

Stéphane Chazelas
źródło
0

Natknąłem się na to przy użyciu rsync w skrypcie bash, który uruchomił polecenie z lub bez -nprzełączania suchych przebiegów. Okazuje się, że rsync i wiele poleceń GNU traktują ''jako ważny pierwszy argument i działają inaczej niż gdyby go nie było.

Debugowanie zajęło trochę czasu, ponieważ parametry zerowe są prawie całkowicie niewidoczne.

Ktoś z listy rsync pokazał mi sposób na uniknięcie tego problemu, jednocześnie znacznie upraszczając moje kodowanie. Jeśli dobrze to rozumiem, jest to wariant ostatniej sugestii @ Stéphane Chazelas.

Zbuduj argumenty poleceń w kilku osobnych zmiennych. Można je ustawić w dowolnej kolejności lub logice odpowiadającej problemowi.

Następnie, na końcu, użyj zmiennych, aby zbudować tablicę ze wszystkim na swoim właściwym miejscu i użyj tego jako argumentu do rzeczywistego polecenia.

W ten sposób polecenie jest wydawane tylko w jednym miejscu w kodzie zamiast powtarzane dla każdej odmiany argumentów.

Każda zmienna, która jest pusta, po prostu znika przy użyciu tej metody.

Wiem, że używanie eval jest bardzo niezadowolone. Nie pamiętam wszystkich szczegółów, ale wydawało mi się, że potrzebuję tego, aby wszystko działało w ten sposób - coś wspólnego z obsługą parametrów z osadzoną białą przestrzenią.

Przykład:

dry_run=''
if [[ it is a test run ]]
then
  dry_run='-n'
fi
...
rsync_options=(
  ${dry_run}
  -avushi
  ${delete}
  ${excludes}
  --stats
  --progress
)
...
eval rsync "${rsync_options[@]}" ...
Joe
źródło
To gorszy sposób robienia tego, co opisałem w pytaniu (warunkowe budowanie tablicy argumentów). Pozostawiając zmienne bez cudzysłowu, kto wie na jakie problemy pozostawiasz siebie otwartym.
muru
@muru Masz rację, ale wciąż potrzebuję czegoś takiego. Nie bardzo rozumiem, jak to naprawić za pomocą technik z innych odpowiedzi. Byłoby wspaniale, gdyby fragment kodu został wykonany we właściwy sposób, który składa go w całość. Później zagram z ostatnią opcją Stephane'a, ponieważ może to zrobić, co chcę.
Joe
Właśnie wróciłem do tego. Dzięki za przykład. To ma sens. Idę z tym pracować.
Joe
Mam tutaj podobny przypadek użycia, ale możesz zrobić coś takiego: if ["$ dry" == "true"]; następnie suche = "- n"; fi ... więc jeśli zmienna dry jest prawdą, to ustawiasz ją na -n, a następnie budujesz komendę rsync jak zwykle i masz to tak: rsync $ {dry1: + "$ dry1"} ... jeśli jest null nic się nie wydarzy, inaczej stanie się -n na twoje polecenie
Freedo