Mengapa cabang 'jika [$ 1 = “1”]' selalu dipilih bahkan jika $ 1 bukan 1?

10

Saya memiliki skrip shell bernama 'teleport.sh' seperti ini:

if [ $1="1" ];
    then
    shift
        mv "$@" ~/lab/Sun
elif [ $1="2" ];
    then
    shift
        mv "$@" ~/lab/Moon
elif [ $1="3" ];
    then
    shift
        mv "$@" ~/lab/Earth
fi

Ketika saya mengeksekusi:

sh teleport.sh 2 testfile

Ini testfiledipindahkan ke ~/lab/Sundirektori, yang membingungkan saya karena saya tidak meneruskan 1 atau '1' ke skrip itu.

Ada apa di sini?

Zen
sumber
1
+1 untuk lab , Matahari , Bulan , Bumi dan teleportasi . Tapi Anda harus selalu ganda quote ekspansi ( $var, $(cmd), dan bahkan `cmd`[yang $(cmd)harus disukai]). Ada beberapa kasus tepi di mana Anda tidak perlu mengutip, tetapi selalu melakukannya tidak akan merugikan.
nyuszika7h
@ nyuszika7h, bukankah seharusnya tanda kutip ganda berarti "$ var" dan "$ cmd"? apa manfaat dari braket bulat yang telah Anda sebutkan di atas?
Zen
$(cmd)adalah substitusi perintah , (kebanyakan) sama dengan `cmd`. Lihat mywiki.wooledge.org/CommandSubstitution dan mywiki.wooledge.org/BashFAQ/082
nyuszika7h

Jawaban:

19

Menggunakan spasi memperbaiki masalah Anda.

if [ "$1" = 1 ];
    then
    shift
        mv "$@" ~/lab/Sun
elif [ "$1" = 2 ];
    then
    shift
        mv "$@" ~/lab/Moon
elif [ "$1" = 3 ];
    then
    shift
        mv "$@" ~/lab/Earth
fi

Padahal ini lebih rapi:

#!/bin/bash

action=$1
shift
files=("$@")
case $action in  
  1) mv -- "${files[@]}" ~/lab/Sun     ;;
  2) mv -- "${files[@]}" ~/lab/Moon    ;;
  3) mv -- "${files[@]}" ~/lab/Earth   ;;
esac
Karlo
sumber
3
Ya, spasi diperlukan, tapi saya bertanya-tanya mengapa $1="1"sintaks asli "berfungsi" sama sekali (menghasilkan hasil yang benar). Bagaimana sebenarnya [/ testbuiltin menafsirkan ungkapan itu?
echristopherson
5
@echristopherson Tanpa spasi, testhanya melihat satu argumen ("nilai-l"). Tanpa argumen lain ("operator" dan "nilai-r"), tidak ada yang perlu diuji, jadi testkatakan saja "ok, well yeah, Anda memberi saya sesuatu dan itu harus benar-benar benar".
Uskup
2
$1="1"adalah $1concatenated oleh=1
jdh8
saat menggunakan kasing, bagaimana cara menetapkan tindakan untuk dieksekusi ketika tidak ada kondisi yang terpenuhi?
Zen
11

Hal pertama yang jelas adalah Anda harus memberi jarak di antara argumen [, testatau [[:

if [ "$1" = 1 ];

Ketika berada di Bash, [[ ]]disarankan menggunakan karena tidak melakukan hal-hal yang tidak perlu untuk ekspresi kondisional seperti pemisahan kata dan perluasan nama path. Mengutip kutipan ganda juga tidak diperlukan. Operator yang lebih mudah dibaca ==juga dapat digunakan.

if [[ $1 == 1 ]];

Ditambahkan catatan: Jika operan kedua juga mengandung variabel, mengutip diperlukan karena dapat dikenakan pencocokan pola jika mengandung karakter dikenali seperti *, ?, [], dll .. Jika diperpanjang globbing atau pencocokan pola diaktifkan dengan shopt -s extglob, bentuk-bentuk lain seperti @(), !(), dll juga akan dikenali sebagai pola. Lihat Pencocokan Pola .

Dengan operator seperti <dan >mungkin masih perlu karena saya pernah menemukan bug di mana tidak mengutip argumen kedua menyebabkan hasil yang berbeda.

Sedangkan untuk operan pertama, tidak ada yang berlaku.

Pertimbangkan variasi yang lebih sederhana ini juga:

case "$1" in
1)
    mv -- "${@:2}" ~/lab/Sun
    ;;
2)
    mv -- "${@:2}" ~/lab/Moon
    ;;
3)
    mv -- "${@:2}" ~/lab/Earth
    ;;
esac

Atau kental:

case "$1" in
1) mv -- "${@:2}" ~/lab/Sun ;;
2) mv -- "${@:2}" ~/lab/Moon ;;
3) mv -- "${@:2}" ~/lab/Earth ;;
esac

"${@:2}"adalah bentuk ekspansi substring atau ekspansi anggota array di mana 2offset. Ini membuat ekspansi dimulai dari nilai kedua. Dengan ini kita mungkin tidak perlu menggunakan shift.

Yang ditambahkan --mencegah mencegah mvnama file dimulai dengan tanda hubung ( -) sebagai opsi yang tidak valid.

konsolebox
sumber
tidakkah kamu harus istirahat dalam setiap kasus?
Archemar
2
@Archemar: tidak, tidak ada fall-through (bertentangan dengan banyak bahasa lain).
Mat
2
@Mat, ada kesalahan jika Anda menggunakan ;&alih-alih ;;(ksh, bash, zsh saja). Tapi kemudian breakmasih tidak mencegah kejatuhan, breakhanya untuk keluar dari lingkaran.
Stéphane Chazelas
Itu bukan argumen untuk iftetapi untuk [perintah.
Stéphane Chazelas
1
Koreksi: tanda kutip ganda tidak hanya diperlukan di sisi kiri! [[ $foo == $bar ]]akan melakukan pencocokan pola, tetapi [[ $foo == "$bar" ]]tidak akan.
nyuszika7h
7

Untuk menjawab pertanyaan mengapa ini terjadi, perilaku [alias testini didokumentasikan dalam POSIX :

Dalam daftar berikut, $ 1, $ 2, $ 3, dan $ 4 mewakili argumen yang disajikan untuk diuji:

[...]

1 argumen:

Keluar true (0) jika $ 1 bukan nol; jika tidak, keluar salah.

Anda memberikan 1 argumen 2=1, yang bukan nol, dan karenanya testkeluar dengan sukses.

Seperti yang ditunjukkan oleh posting lain (dan shellcheck ), jika Anda ingin membandingkan untuk kesetaraan, Anda harus melewati 3 argumen 2, =dan 1.

pria lain itu
sumber
3
Dalam arti ini adalah satu-satunya jawaban yang telah menjawab pertanyaan yang diajukan (daripada memberikan satu atau lebih resep yang melakukan apa yang ingin dicapai OP), dan shell dapat cukup misterius yang tahu mengapa ia melakukan hal-hal yang dilakukannya berguna .
dmckee --- ex-moderator kitten
1

Saya hanya ingin merekomendasikan alternatif yang portabel namun juga lebih rapi. Bash tidak universal (dan jika Anda tidak perlu universal, mengapa Anda menulis skrip shell?)

#! /bin/sh
action="$1"
shift
case "$action" in
    1) dest=Sun   ;;
    2) dest=Moon  ;;
    3) dest=Earth ;;
    *) echo "Unrecognized action code '$action' (must be 1, 2, or 3)" >&2; exit 1 ;;
esac
mv -- "$@" ~/lab/"$dest"

(Catatan untuk pedants: ya, saya tahu tanda kutip $actionpada case "$action" inbaris yang tidak perlu, tapi saya merasa yang terbaik adalah untuk menempatkan mereka di sana pula, sehingga pembaca masa depan tidak harus ingat itu.)

zwol
sumber
1
"Bash tidak universal (dan jika Anda tidak perlu universal, mengapa Anda menulis skrip shell?)" - ini tampaknya menyiratkan bahwa skrip bash tidak memiliki kasus penggunaan.
Ruslan
@Ruslan Ya, itulah pendapat saya. Tulis /bin/shskrip portabel , jika Anda membutuhkannya; jika tidak, tulis dalam bahasa skrip yang tidak kalah mengerikan dari shell. Interpreter Perl dasar pada kenyataannya lebih mungkin untuk hadir dalam lingkungan eksklusif warisan dan lingkungan tertanam cut-down daripada Bash.
zwol