Tại sao nhánh 'if [$ 1 = Ngày 1]] luôn được chọn ngay cả khi $ 1 không phải là 1?


10

Tôi có một tập lệnh shell có tên 'teleport.sh' như thế này:

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

Khi tôi thực thi:

sh teleport.sh 2 testfile

Điều này testfileđược chuyển đến ~/lab/Sunthư mục, điều này làm tôi bối rối rất nhiều vì tôi đã không chuyển 1 hoặc '1' cho tập lệnh đó.

Có chuyện gì ở đây vậy?


1
+1 cho phòng thí nghiệm , Mặt trời , Mặt trăng , Trái đấtdịch chuyển tức thời . Nhưng bạn nên luôn luôn đôi mở rộng quote ( $var, $(cmd), và thậm chí `cmd`[mà $(cmd)nên được ưa thích]). Có một số trường hợp cạnh mà bạn không phải trích dẫn, nhưng luôn luôn làm điều đó sẽ không gây tổn thương.
nyuszika7h

@ nyuszika7h, không nên trích dẫn kép có nghĩa là "$ var" và "$ cmd"? lợi ích của khung tròn bạn đã đề cập ở trên là gì?
Zen

$(cmd)thay thế lệnh , (chủ yếu) giống như `cmd`. Xem mywiki.wooledge.org/CommandSubstitutionmywiki.wooledge.org/BashFAQ/082
nyuszika7h

Câu trả lời:


19

Sử dụng không gian khắc phục vấn đề của bạn.

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

Mặc dù điều này gọn gàng hơn:

#!/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

3
Vâng, các khoảng trắng là cần thiết, nhưng tôi tự hỏi tại sao $1="1"cú pháp ban đầu "hoạt động" cả (tạo ra một kết quả thực sự). Làm thế nào để [/ testbuildin thực sự giải thích biểu hiện đó?
echristopherson

5
@echristopherson Không có khoảng trắng, testchỉ thấy một đối số (một "giá trị l"). Nếu không có các đối số khác ("toán tử" và "giá trị r"), sẽ không có gì để kiểm tra, vì vậy testchỉ cần nói "ok, vâng, bạn đã cho tôi một cái gì đó và điều đó phải hoàn toàn đúng".
giám mục

2
$1="1"được $1nối bởi=1
jdh8

Khi sử dụng case, làm thế nào để thiết lập một hành động để thực thi khi không có điều kiện nào được đáp ứng?
Thiền

11

Điều rõ ràng đầu tiên là bạn nên cung cấp khoảng trắng giữa các đối số của [, testhoặc [[:

if [ "$1" = 1 ];

Khi ở Bash, việc sử dụng [[ ]]được khuyến nghị vì nó không làm những việc không cần thiết cho biểu thức có điều kiện như tách từ và mở rộng tên đường dẫn. Trích dẫn xung quanh dấu ngoặc kép cũng không cần thiết. Một toán tử dễ đọc hơn ==cũng có thể được sử dụng.

if [[ $1 == 1 ]];

Thêm lưu ý: Nếu toán hạng thứ hai cũng chứa biến, trích dẫn là cần thiết vì nó có thể bị phù hợp mô hình nếu nó chứa các ký tự dễ nhận biết như *, ?, [], vv .. Nếu mở rộng globbing hoặc phù hợp với mô hình được kích hoạt với shopt -s extglob, các hình thức khác thích @(), !()vv cũng sẽ được công nhận là mẫu. Xem Khớp mẫu .

Với các toán tử thích <>nó vẫn có thể cần thiết vì tôi đã từng gặp một lỗi trong đó không trích dẫn đối số thứ hai gây ra kết quả khác nhau.

Đối với toán hạng đầu tiên, không có gì áp dụng.

Cũng xem xét biến thể đơn giản hơn này:

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

Hoặc cô đặc:

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

"${@:2}"là một hình thức mở rộng chuỗi con hoặc mở rộng thành viên mảng trong đó 2là phần bù. Điều này làm cho việc mở rộng bắt đầu ở giá trị thứ hai. Với điều này, chúng tôi có thể không cần sử dụng shift.

Việc thêm vào --ngăn không cho mvnhận ra tên tệp bắt đầu bằng dấu gạch ngang ( -) là tùy chọn không hợp lệ.


bạn không nên phá vỡ trong từng trường hợp?
Archemar

2
@Archemar: không, không có thông qua (trái với rất nhiều ngôn ngữ khác).
Mat

2
@Mat, sẽ có thông qua nếu bạn sử dụng ;&thay vì ;;(chỉ ksh, bash, zsh). Nhưng sau đó breakvẫn không ngăn được sự sụp đổ, breakchỉ là thoát ra khỏi các vòng lặp.
Stéphane Chazelas

Đó không phải là đối số ifmà là [lệnh.
Stéphane Chazelas

1
Sửa chữa: dấu ngoặc kép chỉ không cần thiết ở phía bên tay trái! [[ $foo == $bar ]]sẽ thực hiện khớp mẫu, nhưng [[ $foo == "$bar" ]]sẽ không.
nyuszika7h

7

Để trả lời câu hỏi tại sao điều này xảy ra, hành vi này của [aka testđược ghi lại trong POSIX :

Trong danh sách sau đây, $ 1, $ 2, $ 3 và $ 4 đại diện cho các đối số được trình bày để kiểm tra:

[...]

1 đối số:

Thoát đúng (0) nếu $ 1 không rỗng; nếu không, thoát sai.

Bạn đang truyền cho nó 1 đối số, 2=1không phải là null, và do đó testthoát khỏi thành công.

Như bài viết khác (và shellcheck ) chỉ ra, nếu bạn muốn so sánh bình đẳng, bạn sẽ thay phải vượt qua 3 đối số 2, =1.


3
Theo một nghĩa nào đó, đây là câu trả lời duy nhất đã trả lời câu hỏi được hỏi (thay vì đưa ra một hoặc nhiều công thức làm những gì OP muốn thực hiện), và shell có thể đủ bí ẩn để biết tại sao nó làm những việc nó hữu ích .
dmckee --- ex-moderator mèo con

1

Tôi chỉ muốn giới thiệu một thay thế di động nhưng cũng gọn gàng hơn. Bash không phải là phổ quát (và nếu bạn không cần phổ quát, tại sao bạn lại viết một kịch bản 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"

(Lưu ý với các giáo viên: có, tôi biết các trích dẫn $actiontrên case "$action" indòng là không cần thiết, nhưng tôi cảm thấy tốt nhất là đặt chúng ở đó bằng mọi cách, để người đọc trong tương lai không phải nhớ điều đó.)


1
"Bash không phải là phổ quát (và nếu bạn không cần phổ quát, tại sao bạn lại viết một tập lệnh shell?)" - điều này dường như ngụ ý rằng tập lệnh bash không có trường hợp sử dụng.
Ruslan

@Ruslan Vâng, đó là ý kiến ​​của tôi. Viết /bin/shkịch bản di động , nếu bạn cần điều đó; mặt khác, viết bằng ngôn ngữ kịch bản ít tệ hơn shell. Trình thông dịch Perl cơ bản trên thực tế có nhiều khả năng có mặt trong các môi trường độc quyền cũ và các môi trường nhúng bị cắt giảm so với Bash.
zwol
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.