Biến $ BASH_COMMAND dùng để làm gì?


25

Theo hướng dẫn Bash , biến môi trường BASH_COMMANDchứa

Lệnh hiện đang được thực thi hoặc sắp được thực thi, trừ khi shell đang thực thi lệnh là kết quả của một cái bẫy, trong trường hợp đó là lệnh thực thi tại thời điểm bẫy.

Đặt trường hợp góc bẫy đó sang một bên, nếu tôi hiểu chính xác điều này có nghĩa là khi tôi thực thi một lệnh, biến BASH_COMMANDchứa lệnh đó. Không hoàn toàn rõ ràng liệu biến đó có bị hủy sau khi thực thi lệnh hay không (nghĩa là chỉ có sẵn trong khi lệnh đang chạy, nhưng không phải sau đó), mặc dù người ta có thể lập luận rằng đó là "lệnh hiện đang được thực thi hoặc sắp được thực thi" , nó không phải là lệnh vừa được thực thi.

Nhưng hãy kiểm tra:

$ set | grep BASH_COMMAND=
$ 

Trống. Tôi đã dự kiến ​​sẽ thấy BASH_COMMAND='set | grep BASH_COMMAND='hoặc có thể chỉ BASH_COMMAND='set', nhưng trống rỗng làm tôi ngạc nhiên.

Hãy thử một cái gì đó khác:

$ echo $BASH_COMMAND
echo $BASH_COMMAND
$ 

Vâng, điều đó có ý nghĩa. Tôi thực hiện lệnh echo $BASH_COMMANDvà vì vậy biến BASH_COMMANDchứa chuỗi echo $BASH_COMMAND. Tại sao nó làm việc lần này, nhưng không phải trước đây?

Chúng ta hãy làm setlại:

$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$

Vậy còn chờ gì nữa. Nó được thiết lập khi tôi thực hiện echolệnh đó và sau đó nó không được đặt. Nhưng khi tôi thực hiện setlại, BASH_COMMAND không được đặt thành setlệnh. Cho dù tôi có thường xuyên thực hiện setlệnh ở đây không, kết quả vẫn như cũ. Vì vậy, là biến được đặt khi thực hiện echo, nhưng không phải khi thực hiện set? Hãy xem nào.

$ echo Hello AskUbuntu
Hello AskUbuntu
$ set | grep BASH_COMMAND=
BASH_COMMAND='echo $BASH_COMMAND'
$

Gì? Vì vậy, biến được đặt khi tôi thực thi echo $BASH_COMMAND, nhưng không phải khi tôi thực hiện echo Hello AskUbuntu? Sự khác biệt bây giờ ở đâu? Là biến chỉ được đặt khi lệnh hiện tại thực sự buộc shell để đánh giá biến? Hãy thử một cái gì đó khác nhau. Có thể một số lệnh bên ngoài lần này, không phải là một bash dựng sẵn, cho một sự thay đổi.

$ /bin/echo $BASH_COMMAND
/bin/echo $BASH_COMMAND
$ set | grep BASH_COMMAND=
BASH_COMMAND='/bin/echo $BASH_COMMAND'
$

Hmm, ok ... một lần nữa, biến đã được đặt. Vậy dự đoán hiện tại của tôi có đúng không? Là biến chỉ được đặt khi nó phải được đánh giá? Tại sao? Tại sao? Vì lý do hiệu suất? Hãy thử thêm một lần nữa. Chúng tôi sẽ cố gắng grep cho $BASH_COMMANDtrong một tệp và $BASH_COMMANDsau đó sẽ chứa một greplệnh, grepnên grep cho greplệnh đó (nghĩa là cho chính nó). vì vậy hãy tạo một tệp thích hợp:

$ echo -e "1 foo\n2 grep\n3 bar\n4 grep \$BASH_COMMAND tmp" > tmp
$ grep $BASH_COMMAND tmp
grep: $BASH_COMMAND: No such file or directory
tmp:2 grep                                      <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp                    <-- here, the word "grep" is RED
tmp:2 grep                                      <-- here, the word "grep" is RED
tmp:4 grep $BASH_COMMAND tmp                    <-- here, the word "grep" is RED
$ set | grep BASH_COMMAND=
BASH_COMMAND='grep --color=auto $BASH_COMMAND tmp'
$

Ok, thú vị. Lệnh grep $BASH_COMMAND tmpđã được mở rộng thành grep grep $BASH_COMMAND tmp tmp(biến được mở rộng chỉ một lần, dĩ nhiên), và vì vậy tôi đã tham gia grep, một lần trong một tệp $BASH_COMMANDkhông tồn tại và hai lần trong tệp tmp.

Q1: Giả định hiện tại của tôi có đúng không:

  • BASH_COMMANDchỉ được đặt khi một lệnh cố gắng thực sự đánh giá nó; và
  • không được đặt sau khi thực hiện một lệnh, mặc dù mô tả có thể khiến chúng ta tin như vậy?

Q2: Nếu có, tại sao? Hiệu suất? Nếu không, làm thế nào khác hành vi trong chuỗi lệnh trên có thể được giải thích?

Câu 3: Cuối cùng, có kịch bản nào trong đó biến này thực sự có thể được sử dụng một cách có ý nghĩa không? Tôi thực sự đã cố gắng sử dụng nó bên trong $PROMPT_COMMANDđể phân tích lệnh đang được thực thi (và thực hiện một số thứ tùy thuộc vào điều đó), nhưng tôi không thể, vì ngay khi, trong tôi $PROMPT_COMMAND, tôi thực hiện một lệnh để xem biến $BASH_COMMAND, biến được thiết lập để lệnh đó. Ngay cả khi tôi thực hiện MYVARIABLE=$BASH_COMMANDngay từ đầu $PROMPT_COMMAND, sau đó MYVARIABLEchứa chuỗi MYVARIABLE=$BASH_COMMAND, bởi vì một phép gán cũng là một lệnh. (Câu hỏi này không phải là về cách tôi có thể nhận được lệnh hiện tại trong khi $PROMPT_COMMANDthực thi. Có nhiều cách khác, tôi biết.)

Nó hơi giống với nguyên tắc bất định của Heisenberg. Chỉ bằng cách quan sát biến, tôi thay đổi nó.


5
Đẹp, nhưng có lẽ phù hợp hơn cho unix.stackexchange.com ... có bashüber-gurus thực sự ở đó.
Rmano

Chà, có thể di chuyển nó qua đó không? Mod?
Malte Skoruppa

Tôi thực sự không chắc chắn làm thế nào để làm điều đó --- Tôi cho rằng đó là một đặc quyền Mod. Mặt khác, tôi không nghĩ việc gắn cờ là điều hợp lý --- hãy xem ai đó hành động trên lá cờ gần.
Rmano

1
Đối với điểm thứ ba, đây là những gì bạn đang tìm kiếm. Có lẽ dựa trên bài viết đó bạn cũng có thể tìm thấy câu trả lời cho hai điểm đầu tiên.
Radu Rădeanu

2
+1 làm tôi bối rối, nhưng thật vui khi đọc!
Joe

Câu trả lời:


15

Trả lời cho câu hỏi thứ ba: tất nhiên nó có thể được sử dụng một cách có ý nghĩa theo cách trong hướng dẫn sử dụng Bash gợi ý rõ ràng - trong một cái bẫy, ví dụ:

$ trap 'echo ‘$BASH_COMMAND’ failed with error code $?' ERR
$ fgfdjsa
fgfdjsa: command not found
fgfdjsa failed with error code 127
$ cat /etc/fgfdjsa
cat: /etc/fgfdjsa: No such file or directory
cat /etc/fgfdjsa failed with error code 1

Ah, thực sự tốt đẹp. Vì vậy, bạn sẽ nói rằng một cái bẫy là bối cảnh duy nhất mà biến này có ý nghĩa? Nó nghe giống như một trường hợp góc trong hướng dẫn sử dụng: "Lệnh được thực thi (...), trừ khi shell đang thực thi lệnh do bẫy, ..."
Malte Skoruppa

Sau khi đọc các bài viết được liên kết trong câu trả lời của @DocSalvager, rõ ràng $BASH_COMMANDthực sự có thể được sử dụng một cách có ý nghĩa ngay cả trong các bối cảnh khác ngoài một cái bẫy. Tuy nhiên, việc sử dụng bạn mô tả có lẽ là điển hình nhất và đơn giản. Vì vậy, câu trả lời của bạn, và câu trả lời của DocSalvager, trả lời tốt nhất quý 3 của tôi và đây là điều quan trọng nhất đối với tôi. Đối với Q1 và Q2, như được chỉ định bởi @zwets, những điều đó không được xác định. Nó có thể $BASH_COMMANDchỉ được cung cấp một giá trị nếu được truy cập, cho hiệu suất - hoặc nếu không một số điều kiện chương trình nội bộ kỳ lạ có thể dẫn đến hành vi đó. Ai biết?
Malte Skoruppa

1
Trên một sidenote, tôi phát hiện ra rằng bạn có thể buộc $BASH_COMMANDphải được đặt luôn, bằng cách buộc đánh giá $BASH_COMMANDtrước mỗi lệnh. Để làm điều này, chúng ta có thể sử dụng bẫy DEBUG, được thực thi trước mỗi lệnh đơn (tất cả các em). Nếu chúng ta phát hành, ví dụ, trap 'echo "$BASH_COMMAND" > /dev/null' DEBUGthì $BASH_COMMANDsẽ luôn được đánh giá trước mỗi lệnh (và được gán giá trị của lệnh đó $COMMAND). Vì vậy, nếu tôi thực thi set | grep BASH_COMMAND=trong khi cái bẫy đó đang hoạt động ... bạn biết gì? Bây giờ, đầu ra là BASH_COMMAND=set, như mong đợi :)
Malte Skoruppa

6

Bây giờ, quý 3 đã được trả lời (theo ý kiến ​​của tôi: BASH_COMMANDrất hữu ích trong các bẫy và hầu như không ở đâu khác), hãy cho Q1 và Q2 một cú đánh.

Câu trả lời cho Q1 là: tính đúng đắn của giả định của bạn là không thể giải quyết được. Sự thật của cả hai điểm đạn đều không thể được thiết lập, khi họ hỏi về hành vi không xác định. Theo đặc tả của nó, giá trị của BASH_COMMANDđược đặt thành văn bản của lệnh trong thời gian thực hiện lệnh đó. Thông số kỹ thuật không nêu giá trị của nó phải là gì trong bất kỳ tình huống nào khác, tức là khi không có lệnh nào được thực thi. Nó có thể có bất kỳ giá trị hoặc không có gì cả.

Câu trả lời cho câu hỏi 2 "Nếu không, làm thế nào khác có thể giải thích hành vi trong chuỗi lệnh trên?" sau đó theo logic (nếu hơi hướng về mặt giáo dục): nó được giải thích bởi thực tế là giá trị của BASH_COMMANDkhông được xác định. Vì giá trị của nó không được xác định, nó có thể có bất kỳ giá trị nào, đó chính xác là những gì trình tự hiển thị.

Bản thảo

Có một điểm mà tôi nghĩ rằng bạn thực sự đạt được một điểm mềm trong thông số kỹ thuật. Đó là nơi bạn nói:

Ngay cả khi tôi thực hiện MYVARIABLE = $ BASH_COMMAND ngay khi bắt đầu $ PROMPT_COMMAND, thì MYVARIABLE cũng chứa chuỗi MYVARIABLE = $ BASH_COMMAND, vì chuyển nhượng cũng là một lệnh .

Cách tôi đọc trang bash man, bit in nghiêng là không đúng. Phần này SIMPLE COMMAND EXPANSIONgiải thích cách các phép gán biến đầu tiên trên dòng lệnh được đặt sang một bên và sau đó

Nếu không có kết quả tên lệnh [nói cách khác, chỉ có các phép gán biến], các phép gán biến ảnh hưởng đến môi trường shell hiện tại.

Điều này gợi ý cho tôi rằng các bài tập biến không phải là các lệnh (và do đó được miễn hiển thị BASH_COMMAND), giống như trong các ngôn ngữ lập trình khác. Điều này sau đó cũng sẽ giải thích tại sao không có một dòng BASH_COMMAND=settrong đầu ra set, setvề cơ bản là một cú pháp 'đánh dấu' cho phép gán biến.

OTOH, trong đoạn cuối của phần đó, nó nói

Nếu có một tên lệnh còn lại sau khi mở rộng, thực hiện tiến hành như được mô tả dưới đây. Nếu không, lệnh thoát.

... Điều này cho thấy khác, và các bài tập biến cũng là các lệnh.


1
Chỉ vì một số giả định không thể được thiết lập không có nghĩa là nó không chính xác. Einstein cho rằng tốc độ ánh sáng là như nhau đối với bất kỳ người quan sát nào (ở bất kỳ tốc độ nào) là một giả thuyết cơ bản cho lý thuyết tương đối; Điều đó không thể được thiết lập, nhưng điều này không có nghĩa là nó không chính xác. Các thuộc tính "có thể được thiết lập" và "là chính xác" là trực giao. Tốt nhất bạn có thể nói "Chúng tôi không biết liệu nó có đúng không, vì nó không thể được thiết lập". Ngoài ra, nó được chỉ định: đó là lệnh hiện tại trong khi thực hiện. Vì vậy, nếu tôi gõ settôi sẽ thấy BASH_COMMAND='set'ở đâu đó trong đầu ra đó, đó là
Malte Skoruppa

... tuy nhiên không phải vậy. Mặc dù đó là trong khi thực hiện setlệnh . Do đó, theo thông số kỹ thuật nó nên hoạt động. Vì vậy, đó là việc thực hiện không trung thành tuân theo các thông số kỹ thuật, hoặc tôi hiểu sai các thông số kỹ thuật? Tìm câu trả lời cho câu hỏi đó là loại mục đích của Q1. +1 cho phần tái bút của bạn :)
Malte Skoruppa

@MalteSkoruppa Điểm tốt. Đã sửa câu trả lời cho phù hợp và thêm nhận xét về set.
zwets

1
Có thể trong trình thông dịch bash, setkhông phải là "lệnh". Cũng như =không phải là một "mệnh lệnh". Việc xử lý văn bản theo sau / xung quanh các mã thông báo đó có thể có logic hoàn toàn khác với việc xử lý một "lệnh".
DocSalvager

Điểm tốt từ cả hai bạn. :) Nó cũng có thể setkhông được tính là một "lệnh". Tôi đã không nghĩ rằng nó $BASH_COMMANDsẽ trở nên quá kém trong nhiều trường hợp. Tôi đoán ít nhất chúng ta đã thiết lập rằng trang người đàn ông chắc chắn thể rõ ràng hơn về nó;)
Malte Skoruppa

2

Sử dụng sáng tạo cho $ BASH_COMMAND

Gần đây đã tìm thấy cách sử dụng ấn tượng này của $ BASH_COMMAND trong việc triển khai chức năng giống như macro.

Đây là thủ thuật cốt lõi của bí danh và thay thế việc sử dụng bẫy DEBUG. Nếu bạn đọc phần trong bài viết trước về bẫy DEBUG, bạn sẽ nhận ra biến $ BASH_COMMAND. Trong bài đăng đó tôi đã nói rằng nó được đặt thành văn bản của lệnh trước mỗi cuộc gọi đến bẫy DEBUG. Chà, hóa ra nó được thiết lập trước khi thực hiện mọi lệnh, bẫy DEBUG hoặc không (ví dụ: chạy 'echo Hồi lệnh này = $ BASH_COMMAND' để xem tôi đang nói về điều gì). Bằng cách gán cho nó một biến (chỉ cho dòng đó), chúng tôi thu được BASH_COMMAND ở phạm vi ngoài cùng của lệnh, sẽ chứa toàn bộ lệnh.

Bài viết trước của tác giả cũng cung cấp một số nền tảng tốt trong khi thực hiện một kỹ thuật sử dụng DEBUG trap. Các trapđược loại bỏ trong phiên bản được cải thiện.


+1 Cuối cùng tôi cũng có được để đọc hai bài báo đó. Ồ Thật là một đọc! Rất giác ngộ. Gã đó là một bậc thầy bash ! Thanh danh! Điều này cũng trả lời nhận xét của tôi về câu trả lời của Dmitry về việc liệu $BASH_COMMANDcó thể được sử dụng một cách có ý nghĩa trong bối cảnh khác không trap. Và thật là một công dụng! Sử dụng nó để thực hiện các lệnh macro trong bash - ai có thể nghĩ rằng nó có thể? Cảm ơn con trỏ.
Malte Skoruppa

0

Một cách sử dụng dường như phổ biến cho bẫy ... gỡ lỗi là để cải thiện các tiêu đề xuất hiện trong danh sách cửa sổ (^ A ") khi bạn đang sử dụng" màn hình ".

Tôi đã cố gắng làm cho "màn hình", "danh sách cửa sổ" trở nên tiện dụng hơn nên tôi bắt đầu tìm các bài viết tham khảo "bẫy ... gỡ lỗi".

Tôi thấy rằng phương thức sử dụng PROMPT_COMMAND để gửi "chuỗi tiêu đề null" không hoạt động tốt, vì vậy tôi đã hoàn nguyên về phương pháp gỡ lỗi bẫy.

Đây là cách bạn làm điều đó:

1 Nói "màn hình" để tìm chuỗi thoát (bật nó) bằng cách đặt "shelltitle '$ | bash:'" vào "$ HOME / .screenrc".

2 Tắt sự lan truyền của bẫy gỡ lỗi thành các vỏ phụ. Điều này rất quan trọng, bởi vì nếu nó được kích hoạt, mọi thứ sẽ được xử lý, sử dụng: "set + o functrace".

3 Gửi trình tự thoát tiêu đề cho "màn hình" để giải thích: bẫy 'printf "\ ek $ (ngày +% Y% m% d% H% M% S) $ (whoami) @ $ (tên máy chủ): $ (pwd) $ {BASH_COMMAND} \ e \ "'" DEBUG "

Nó không hoàn hảo, nhưng nó có ích, và bạn có thể sử dụng phương pháp này để đặt bất cứ thứ gì bạn thích vào tiêu đề

Xem "danh sách cửa sổ" sau (5 màn hình):

Cờ tên

1 bash: 20161115232035 mcb @ ken007: / home / mcb / ken007 RSYNCCMD = "sudo rsync" myrsync.sh -R -r "$ {1}" "$ {BACKUPDIR} $ {2: +" / $ {2} " } "$ 2 bash: 20161115230434 mcb @ ken007: / home / mcb / ken007 ls --color = auto -la $ 3 bash: 20161115230504 mcb @ ken007: / home / mcb / ken007 20161115222415 mcb @ ken007: / home / mcb / ken007 ssh ken009 $ 5 bash: 20161115222450 mcb @ ken007: / home / mcb / ken007 mycommoncleanup $

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.