Với các biến Bash, sự khác biệt giữa $ myvar và Hồi $ myvar 'là gì? (Hành vi kỳ quặc cụ thể)


2

Câu hỏi chung:

Trong Bash, tôi biết rằng việc sử dụng biến myvarcó thể được thực hiện theo hai cách:

# Define a variable:
bash$ myvar="two words"

# Method one to dereference:
bash$ echo $myvar
two words

# Method two to dereference:
bash$ echo "$myvar"
two words

Trong trường hợp trên, hành vi là giống hệt nhau. Điều này là do cách làm echoviệc. Trong các tiện ích Unix khác, việc các từ được nhóm lại với nhau bằng dấu ngoặc kép sẽ tạo ra sự khác biệt lớn:

bash$ myfile="Cool Song.mp3"
bash$ rm "$myfile"            # Deletes "Cool Song.mp3".
bash$ rm $myfile              # Tries to delete "Cool" and "Song.mp3".

Tôi tự hỏi ý nghĩa sâu xa của sự khác biệt này là gì. Quan trọng nhất, làm thế nào tôi có thể xem chính xác những gì sẽ được truyền cho lệnh, để tôi có thể xem nếu nó được trích dẫn đúng?

Ví dụ về tỷ lệ cược cụ thể:

Tôi sẽ chỉ viết mã với hành vi được quan sát:

bash$ mydate="--date=format:\"%Y-%m-%d T%H\""
bash$ git log "$mydate"    # This works great.
bash$ git log $mydate
fatal: ambiguous argument 'T%H"': unknown revision or path not in the working tree.

Tại sao tôi cần dấu ngoặc kép? Chính xác thì git-log nhìn thấy gì sau khi biến bị hủy đăng ký mà không có dấu ngoặc kép?

Nhưng bây giờ hãy xem điều này:

bash$ nospace="--date=format:\"%Y-%m-%d\""
bash$ git log $nospace        # Now THIS works great.
bash$ git log "$nospace"      # This kind of works, here is a snippet:

# From git-log output:
Date:   "2018-04-12"

Yuck, tại sao đầu ra in có dấu ngoặc kép trong đó bây giờ? Có vẻ như nếu trích dẫn kép là không cần thiết, chúng không bị tước, chúng được hiểu là các ký tự trích dẫn theo nghĩa đen nếu và chỉ khi chúng không cần thiết.

Git được thông qua là đối số là gì? Tôi ước tôi biết làm thế nào để tìm hiểu.

Để làm cho vấn đề trở nên phức tạp hơn, tôi đã viết một kịch bản Python bằng cách argparsechỉ in tất cả các đối số (như Bash đã giải thích chúng, do đó, với các trích dẫn kép mà Bash nghĩ rằng chúng là một phần của đối số và với các từ được nhóm hoặc không được nhóm lại thành Bash thấy phù hợp) và argparsetập lệnh Python hành xử rất hợp lý. Đáng buồn thay, tôi nghĩ argparsecó thể đang âm thầm sửa chữa một vấn đề đã biết với Bash và do đó che khuất những thứ lộn xộn mà Bash đang truyền cho nó. Đó chỉ là một phỏng đoán, tôi không biết. Có lẽ git-log đang bí mật làm hỏng những gì Bash đang truyền cho nó.

Hoặc có lẽ tôi chỉ không biết chuyện gì đang xảy ra.

Cảm ơn.

Chỉnh sửa Chỉnh sửa: Hãy để tôi nói điều này ngay bây giờ, trước khi có bất kỳ câu trả lời nào: Tôi biết rằng tôi có thể sử dụng các trích dẫn duy nhất xung quanh toàn bộ và sau đó không thoát khỏi dấu ngoặc kép. Điều này thực sự hoạt động tốt hơn đối với vấn đề ban đầu của tôi khi sử dụng git-log, nhưng tôi đã thử nghiệm nó trong một số bối cảnh khác và nó cũng khó đoán và không đáng tin cậy như nhau. Một cái gì đó kỳ lạ đang diễn ra với trích dẫn bên trong các biến. Tôi thậm chí sẽ không đăng tất cả những điều kỳ lạ đã xảy ra với dấu ngoặc đơn.

Chỉnh sửa 2 - Điều này cũng không hoạt động: Tôi chỉ có ý tưởng tuyệt vời này, nhưng nó hoàn toàn không hoạt động:

bash$ mydate="--date=format:%Y-%m-%d\ T%H"
bash$ git log "$mydate"

# Git log output has this:
Date:   2018-04-12\ T23

Vì vậy, nó không có dấu ngoặc kép, nhưng nó có ký tự dấu gạch chéo theo nghĩa đen trong chuỗi ngày. Ngoài ra, git log $mydatekhông có lỗi trích dẫn nào, với dấu gạch chéo ngược trong không gian.


Đây có phải là Q về git không? Hay khoảng trắng?
Xen2050

@ Xen2050 Tôi thực sự không chắc vấn đề có liên quan đến Git hay không. Tôi khá chắc chắn rằng nó liên quan đến Bash. Có thể là Git đã phá vỡ một cái gì đó, hoặc đối số của Python đã sửa một cái gì đó, bởi vì chúng có hành vi khác nhau. Ngoài ra, giá trị tôi thực sự muốn chứa -, =,:, [dấu cách],% và cả trích dẫn kép hoặc trích dẫn đơn, vì vậy nó có thể là một giá trị rất khó sử dụng.
SerMetAla

Câu trả lời:


4

Phương pháp khác nhau:

Khi bạn chạy git log --format="foo bar", những trích dẫn đó không được giải thích bởi git - chúng bị xóa bởi trình bao (và bảo vệ văn bản được trích dẫn khỏi bị chia tách). Điều này dẫn đến một đối số duy nhất:

  • --format=foo bar

Tuy nhiên, khi các biến không được trích dẫn được mở rộng, kết quả sẽ chuyển qua phân tách từ, nhưng không thông qua bỏ qua. Vì vậy, nếu biến của bạn chứa --format="foo bar", nó được mở rộng thành các đối số sau:

  • --format="foo
  • bar"

Điều này có thể được xác minh bằng cách sử dụng:

  • biến printf '% s \ n' $

... Cũng như bất kỳ tập lệnh đơn giản nào in các đối số nhận được.

  • #! / usr / bin / env perl
    với $ i (0 .. $ # ARGV) {
        in ($ i + 1). "=". $ ARGV [$ i]. "\ n";
    }
    
  • #! / usr / bin / env python3
    nhập khẩu hệ thống
    đối với tôi, arg trong liệt kê (sys.argv):
        in (i, "=", arg)
    

Nếu bạn luôn có sẵn bash, cách giải quyết ưa thích là sử dụng các biến mảng :

myvar=( --format="foo bar" )

Với điều này, phân tích cú pháp thông thường được thực hiện trong khi gán, không phải trong quá trình mở rộng. Bạn sử dụng cú pháp này để mở rộng nội dung của biến, mỗi phần tử có đối số riêng:

git log "${myvar[@]}"

Tôi đã chấp nhận câu trả lời này, nó hữu ích hơn nhiều so với câu trả lời khác. Cảm ơn bạn.
SerMetAla

Vui lòng thêm điều này ở trên cùng, bởi vì đó là mấu chốt của câu trả lời: mydate="--date=format:%Y-%m-%d T%H"Điều này không sử dụng các biến mảng (rất tuyệt vời) và nó nhấn mạnh giải pháp làm việc chỉ thay đổi một ký tự từ mã vấn đề ban đầu. Cảm ơn.
SerMetAla

@SerMetAla Tôi chỉ tò mò: vấn đề cụ thể là gì khi chỉ sử dụng phương pháp tôi đã trình bày, nghĩa là mydate="--date=format:%Y-%m-%d T%H"git log "$mydate"?
slhck

@slhck Tôi nghĩ rằng câu trả lời lý tưởng sẽ là câu trả lời này, mà tôi đã chấp nhận, cộng với một đoạn trích từ câu trả lời của bạn, lý tưởng được đặt ở đầu câu trả lời này. Tôi thực sự sẽ sử dụng một đoạn trích từ câu trả lời của bạn, cảm ơn bạn. Câu trả lời này chứa một lời giải thích chính xác về cách xem git-log sẽ thực sự nhìn thấy gì, bằng cách sử dụng printftrong chính Bash (tôi đã kiểm tra) hoặc Python (tôi đã kiểm tra) hoặc Perl (Tôi không kiểm tra, tôi tin tưởng nó bằng phép ngoại suy). Câu trả lời này cũng nhấn mạnh những gì cơ bản đang diễn ra trong đoạn trích hữu ích của bạn: Bash đang thêm trích dẫn kép mà nó cần, vì vậy tôi không nên gõ nó.
SerMetAla

3
Tôi tin rằng tôi đã trả lời rằng trong "Sử dụng mảng".
grawity

2

Tại sao lệnh ban đầu của bạn không hoạt động?

bash$ mydate="--date=format:\"%Y-%m-%d T%H\""
bash$ git log "$mydate"    # This works great.
bash$ git log $mydate
fatal: ambiguous argument 'T%H"': unknown revision or path not in the working tree.

Bạn hỏi:

Tại sao tôi cần dấu ngoặc kép? Chính xác thì git-log nhìn thấy gì sau khi biến bị hủy đăng ký mà không có dấu ngoặc kép?

Nếu bạn không sử dụng dấu ngoặc kép xung quanh $mydate, biến sẽ được mở rộng nguyên văn và dòng shell sẽ là dòng sau đây trước khi được thực thi:

git log --date=format:"%Y-%m-%d T%H"
                      ^————————————^—————— literal quotes

Ở đây, bạn (không cần thiết) đã thêm các trích dẫn bằng chữ bằng cách sử dụng \"trong phép gán biến.

Kể từ khi lệnh sẽ trải qua tách từ , gitsẽ nhận được ba đối số, log, --date-format:"%Y-%m%-dT%H", do đó phàn nàn về việc không tìm thấy bất kỳ cam kết hoặc đối tượng có tên T%H".


Cách tiếp cận đúng là gì?

Nếu bạn muốn giữ các đối số cùng nhau, nếu đối số đó chứa khoảng trắng, bạn phải bọc đối số trong dấu ngoặc kép. Nói chung, luôn luôn bọc các biến trong dấu ngoặc kép.

Điều này hoạt động ngay cả khi có một không gian bên trong biến:

mydate="--date=format:%Y-%m-%d T%H"
git log "$mydate"

Bây giờ đối số thứ ba gitsẽ là $mydate, bao gồm cả không gian mà bạn đã chỉ định ban đầu. Tất cả các trích dẫn được tước bởi vỏ trước khi được chuyển đến git.

Bạn chỉ cần không cần trích dẫn bổ sung nếu tất cả những gì bạn muốn là gitxem một đối số, bao bọc đối số đó trong dấu ngoặc kép khi truyền biến "$mydate".


Ngoài ra, bạn hỏi:

bash$ nospace="--date=format:\"%Y-%m-%d\""
bash$ git log $nospace        # Now THIS works great.
bash$ git log "$nospace"      # This kind of works, here is a snippet:

# From git-log output:
Date:   "2018-04-12"

Câu hỏi của bạn:

Yuck, tại sao đầu ra in có dấu ngoặc kép trong đó bây giờ?

Bởi vì bạn lại bao gồm các trích dẫn bằng chữ trong đối số (bằng cách thoát chúng), được chuyển thành, giả sử, các trích dẫn thực tế của Hồi khi bạn quên trích dẫn biến trong lệnh thực tế của mình. Tôi nói là quên quên vì sử dụng các biến không được trích dẫn trong các lệnh shell thường chỉ khiến bạn gặp rắc rối và ở đây nó đảo ngược một lỗi bạn đã mắc phải trong khi chỉ định biến ở vị trí đầu tiên.

Tái bút: Tôi biết điều này thật khó hiểu, nhưng đó là Bash, và nó tuân theo một số quy tắc rõ ràng. Không có lỗi ở đây. Một bài luận liên quan về tên tệp trong shell cũng rất tiết lộ, vì nó liên quan đến vấn đề xử lý khoảng trắng trong Bash.


Bạn đã nói một số điều đúng, tuy nhiên, lệnh sau đây không hoạt động và đó là điều tôi muốn: git log --date=format:"%Y-%m-%d T%H"Bạn nói nó như thể nó không hoạt động, nhưng nó hoạt động. Ngoài ra, không có cách nào khác để làm cho nó hoạt động.
SerMetAla

Vâng, tất nhiên lệnh đó hoạt động khi bạn gõ nó trực tiếp. Nó chỉ không hoạt động khi lần đầu tiên bạn gán đối số (định dạng ngày) cho một biến chứa các trích dẫn bằng chữ và sau đó mở rộng biến đó.
slhck

@SerMetAla Sự khác biệt quan trọng là giữa các trích dẫn cú pháp (đi xung quanh dữ liệu và là những gì bạn muốn) và các trích dẫn bằng chữ (là một phần của dữ liệu và là những gì bạn nhận được khi đặt dấu ngoặc kép vào giá trị của biến).
Gordon Davisson
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.