Operator trójskładnikowy (? :) w Bash


442

Czy istnieje sposób na zrobienie czegoś takiego?

int a = (b == 5) ? c : d;

używasz Bash?


1
Odpowiedź @ dutCh pokazuje, że bashma coś podobnego do „operatora trójskładnikowego”, jednak w bashtym przypadku nazywa się go „operatorem warunkowym” expr?expr:expr(patrz man bashsekcja goto „Ocena arytmetyczna”). Należy pamiętać, że bash„operator warunkowy” jest trudny i ma pewne problemy.
Trevor Boyd Smith

Bash ma trójskładnikowy operator liczb całkowitych i działa wewnątrz wyrażenia arytmetycznego ((...)). Zobacz Shell Arithmetic .
codeforester

1
Tak jak wspomniano @codeforester, operator trójskładnikowy pracuje z rozszerzeniem $(( ))arytmetycznym i oceną arytmetyczną (( )). Zobacz także https://mywiki.wooledge.org/ArithmeticExpression.
Kai

Odpowiedzi:


490

operator trójskładnikowy ? :jest po prostu krótką formąif/else

case "$b" in
 5) a=$c ;;
 *) a=$d ;;
esac

Lub

 [[ $b = 5 ]] && a="$c" || a="$d"

103
Zauważ, że =operator sprawdza równość ciągów, a nie równość liczbową (tzn. [[ 05 = 5 ]]Jest fałszem). Jeśli chcesz porównać numerycznie, użyj -eqzamiast tego.
Gordon Davisson

10
Jest to raczej krótka formaif/then/else
vol7ron

15
To genialny sposób na wykorzystanie zachowania zwarciowego w celu uzyskania efektu operatora trójskładnikowego :) :) :)
mtk

6
dlaczego [i]? działa tak samo dobrze: [$ b = 5] && a = "$ c" || a = "$ d"
kdubs

83
cond && op1 || op2Konstrukt ma wrodzoną błąd: jeśli op1ma niezerowy kod zakończenia bez względu na przyczynę, wynik będzie cicho stać op2. if cond; then op1; else op2; fijest również jedną linią i nie ma tej wady.
ivan_pozdeev

375

Kod:

a=$([ "$b" == 5 ] && echo "$c" || echo "$d")

58
jest to lepsze niż inne ... chodzi o to, że operator trzeciorzędny jest operatorem, stąd jego właściwy kontekst jest wyrażony, dlatego musi zwrócić wartość.
nic ferrier

2
To jest najbardziej zwięzły sposób. Pamiętaj, że jeśli część z echo "$c"jest aliasowanym, wielowierszowym poleceniem (jak echo 1; echo 2), powinieneś umieścić go w nawiasach.
Matt

2
Spowoduje to również przechwycenie dowolnego wyniku testowanego polecenia (tak, w tym konkretnym przypadku „wiemy”, że nie wytwarza żadnego).
ivan_pozdeev

8
Ten łańcuch operatorów zachowuje się jak operator trójskładnikowy tylko wtedy, gdy masz pewność, że polecenie po &&nie będzie miało niezerowego statusu wyjścia. W przeciwnym razie a && b || c „niespodziewanie” zadziała , cjeśli się apowiedzie, ale się bnie powiedzie.
chepner

155

Jeśli warunkiem jest jedynie sprawdzenie, czy zmienna jest ustawiona, istnieje jeszcze krótsza forma:

a=${VAR:-20}

przypisze awartość VARif VARustawioną, w przeciwnym razie przypisze jej wartość domyślną 20- może to być również wynikiem wyrażenia.

Jak zauważa Alex w komentarzu, takie podejście jest technicznie nazywane „rozszerzaniem parametrów”.


6
W przypadku przekazania parametru ciągu z łącznikami w nim musiałem użyć znaków cudzysłowu: a=${1:-'my-hyphenated-text'}
saranicole 26.04.16

1
link dla leniwych - istnieją dodatkowe operatory niż tylko substytut ( :-)
Justin Wrobel

10
@JustinWrobel - niestety nie ma takiej składni ${VAR:-yes:-no}.
Ken Williams,

76
if [ "$b" -eq 5 ]; then a="$c"; else a="$d"; fi

cond && op1 || op2Wyrażenie sugerowane w innych odpowiedzi ma wrodzoną błąd: jeśli op1ma status wyjścia niezerowe, op2cicho staje się wynikiem; błąd również nie zostanie wykryty w -etrybie. Tak, że ekspresja jest bezpieczny w użyciu, jeśli tylko op1może nigdy nie uda (na przykład :, truejeśli wbudowanym poleceniem lub przypisania zmiennej bez żadnych operacji, które może zakończyć się niepowodzeniem (np podziałów i połączeń OS)).

Zwróć uwagę na ""cytaty. Pierwsza para zapobiegnie błędowi składni, jeśli $bjest pusta lub ma spacje. Inne zapobiegną tłumaczeniu całej białej spacji na pojedyncze spacje.


1
Widziałem także prefiks, który, jak sądzę, [ "x$b" -eq "x" ]służył do testowania konkretnego pustego łańcucha. Lubię używać składni używanych przez zsh („PARAMETER EXPANSION” na stronie zshexpn), ale niewiele wiem o ich przenośności.
John P


46
(( a = b==5 ? c : d )) # string + numeric

31
Jest to dobre dla porównań numerycznych i przypisań, ale da nieprzewidywalne wyniki, jeśli użyjesz go do porównań ciągów i przypisań ... (( ))traktuje wszystkie / wszystkie ciągi jako0
Peter.O

3
Można to również napisać:a=$(( b==5 ? c : d ))
joeytwiddle

33
[ $b == 5 ] && { a=$c; true; } || a=$d

Pozwoli to uniknąć wykonywania części po || przez przypadek, gdy kod między && a || zawodzi.


To nadal nie -e(set -o errexit; [ 5 == 5 ] && { false; true; echo "success"; } || echo "failure"; echo $?; echo "further on";)success 0 further on
wychwytuje

2
Użyj :wbudowanego zamiast, trueaby zapisać exec-zewnętrzny program.
Tom Hale,

@ivan_pozdeev Czy jest jakiś sposób, aby użyć &&.. ||i nadal złapać między nimi nieudane polecenie?
Tom Hale,

2
@TomHale No. &&i ||z definicji mają zastosowanie do całego polecenia przed nimi. Tak więc, jeśli masz przed sobą dwa polecenia (niezależnie od ich połączenia), nie możesz zastosować go tylko do jednego z nich, a nie do drugiego. Państwo może emulować if/ then/ elselogiki ze zmiennymi flagi, ale po co, jeśli istnieje if/ then/ elsewłaściwa?
ivan_pozdeev

14

Komenda let obsługuje większość podstawowych operatorów, których potrzeba:

let a=b==5?c:d;

Oczywiście działa to tylko w przypadku przypisywania zmiennych; nie może wykonywać innych poleceń.


24
jest dokładnie równoważne z ((...)), więc jest ważne tylko dla wyrażeń arytmetycznych
drAlberT 30.01.2013

13

Oto kolejna opcja, w której musisz określić zmienną, którą przypisujesz tylko raz, i nie ma znaczenia, czy przypisujesz ciąg, czy liczbę:

VARIABLE=`[ test ] && echo VALUE_A || echo VALUE_B`

Tylko myśl. :)


Główny minus: uchwyci również standard [ test ]. Tak więc konstrukcja jest bezpieczna w użyciu tylko wtedy, gdy „wiesz”, że polecenie nie wypisuje niczego na standardowe wyjście.
ivan_pozdeev

Również to samo co stackoverflow.com/questions/3953645/ternary-operator-in-bash/... plus potrzeba cytowania i ucieczki cytatów w środku. Będzie wyglądać wersja z tolerancją przestrzeni VARIABLE="`[ test ] && echo \"VALUE_A\" || echo \"VALUE_B\"`".
ivan_pozdeev

8

Wydaje się, że w moich przypadkach użycia działają:

Przykłady

$ tern 1 YES NO                                                                             
YES

$ tern 0 YES NO                                                                             
NO

$ tern 52 YES NO                                                                            
YES

$ tern 52 YES NO 52                                                                         
NO

i może być używany w skrypcie takim jak:

RESULT=$(tern 1 YES NO)
echo "The result is $RESULT"

terno

function show_help()
{
  echo ""
  echo "usage: BOOLEAN VALUE_IF_TRUE VALUE_IF_FALSE {FALSE_VALUE}"
  echo ""
  echo "e.g. "
  echo ""
  echo "tern 1 YES NO                            => YES"
  echo "tern 0 YES NO                            => NO"
  echo "tern "" YES NO                           => NO"
  echo "tern "ANY STRING THAT ISNT 1" YES NO     => NO"
  echo "ME=$(tern 0 YES NO)                      => ME contains NO"
  echo ""

  exit
}

if [ "$1" == "help" ]
then
  show_help
fi
if [ -z "$3" ]
then
  show_help
fi

# Set a default value for what is "false" -> 0
FALSE_VALUE=${4:-0}

function main
{
  if [ "$1" == "$FALSE_VALUE" ]; then
    echo $3
    exit;
  fi;

  echo $2
}

main "$1" "$2" "$3"

7
Bardzo objaśniające. Bardzo kompletne. Bardzo gadatliwy. Trzy rzeczy, które lubię. ;-)
Jesse Chisholm

2
jest ternczęścią Bash? Wydaje się, że Mac go nie ma
nonpolarity

4
hej @ 太極 者 無極 而 生 - to nazwa skryptu bash - zapisz powyższą sekcję „tern” w pliku o nazwie „tern”, a następnie uruchom chmod 700 ternw tym samym folderze. Teraz będziesz mieć ternpolecenie w swoim terminalu
Brad Parks

8

W skrypcie powłoki możemy zastosować następujące trzy sposoby dla operatora trójskładnikowego:

    [ $numVar == numVal ] && resVar="Yop" || resVar="Nop"

Or

    resVar=$([ $numVar == numVal ] && echo "Yop" || echo "Nop")

Or

    (( numVar == numVal ? (resVar=1) : (resVar=0) ))

Jest to właściwie (pierwszy i drugi z tych trzech przykładów) najbardziej odpowiednia odpowiedź na to pytanie IMHO.
netpoetica,

6

Istnieje również bardzo podobna składnia dla trójkowych warunków warunkowych w bash:

a=$(( b == 5 ? 123 : 321  ))

Niestety, działa tylko z liczbami - o ile wiem.


5
(ping -c1 localhost&>/dev/null) && { echo "true"; } || {  echo "false"; }

4

Możesz użyć tego, jeśli chcesz podobną składnię

a=$(( $((b==5)) ? c : d ))

Działa to tylko z liczbami całkowitymi. Możesz napisać to prościej, ponieważ a=$(((b==5) ? : c : d))- $((...))jest potrzebne tylko wtedy, gdy chcemy przypisać wynik arytmetyki do innej zmiennej.
codeforester

2

Oto kilka opcji:

1- Użyj, jeśli jeszcze w jednym wierszu, jest to możliwe.

if [[ "$2" == "raiz" ]] || [[ "$2" == '.' ]]; then pasta=''; else pasta="$2"; fi

2- Napisz taką funkcję:

 # Once upon a time, there was an 'iif' function in MS VB ...

function iif(){
  # Echoes $2 if 1,banana,true,etc and $3 if false,null,0,''
  case $1 in ''|false|FALSE|null|NULL|0) echo $3;;*) echo $2;;esac
}

użyj wewnętrznego skryptu w ten sposób

result=`iif "$expr" 'yes' 'no'`

# or even interpolating:
result=`iif "$expr" "positive" "negative, because $1 is not true"` 

3- Zainspirowany w odpowiedzi na przypadek, bardziej elastyczne i jedno-liniowe użycie to:

 case "$expr" in ''|false|FALSE|null|NULL|0) echo "no...$expr";;*) echo "yep $expr";;esac

 # Expression can be something like:     
   expr=`expr "$var1" '>' "$var2"`

2

Oto ogólne rozwiązanie

  • działa również z testami ciągów
  • wydaje się raczej wyrażeniem
  • pozwala uniknąć subtelnych skutków ubocznych, gdy warunek się nie powiedzie

Test z porównaniem numerycznym

a = $(if [ "$b" -eq 5 ]; then echo "$c"; else echo "$d"; fi)

Przetestuj za pomocą porównania ciągów

a = $(if [ "$b" = "5" ]; then echo "$c"; else echo "$d"; fi)


1

To bardzo przypomina dobrą odpowiedź Vladimira . Jeśli twoje „trójskładnikowe” to „jeśli prawda, ciąg, jeśli fałsz, puste”, możesz po prostu zrobić:

$ c="it was five"
$ b=3
$ a="$([[ $b -eq 5 ]] && echo "$c")"
$ echo $a

$ b=5
$ a="$([[ $b -eq 5 ]] && echo "$c")"
$ echo $a
it was five

1

odpowiedzieć na: int a = (b == 5) ? c : d;

tylko napisz:

b=5
c=1
d=2
let a="(b==5)?c:d"

echo $a # 1

b=6;
c=1;
d=2;
let a="(b==5)?c:d"

echo $a # 2

pamiętaj, że „wyrażenie” jest równoważne $ ((wyrażenie))


0

Alternatywa zorientowana na łańcuch, która wykorzystuje tablicę:

spec=(IGNORE REPLACE)
for p in {13..15}; do
  echo "$p: ${spec[p==14]}";
done

które wyjścia:

13: IGNORE
14: REPLACE
15: IGNORE
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.