Có thể truy cập vào dòng lệnh hoàn chỉnh bao gồm các đường ống trong tập lệnh bash không?


8

Ví dụ: dòng lệnh:

test.sh arg1 | grep "xyz"

Có thể lấy dòng lệnh hoàn chỉnh bao gồm grep sau trong tập lệnh bash test.sh không?


bạn có thể làm rõ những gì bạn có nghĩa là "dòng lệnh"?
Bart

Tôi chỉ tự hỏi liệu có một biến đô la đặc biệt có chứa chuỗi hoàn chỉnh (dòng lệnh) không chỉ là tên tập lệnh và các đối số của nó
hellcode 23/07/19

2
Trường hợp sử dụng của bạn để làm điều này là gì?
Kusalananda

9
@hellcode bạn không cần biết nếu bạn đang ở trong một đường ống cho điều đó. Chỉ cần kiểm tra nếu đầu ra là một TTY. [ -t 1 ] unix.stackexchange.com/a/401938/70524
muru

Câu trả lời:


6

Không có cách nào để làm điều đó nói chung .

Nhưng một bashlớp vỏ tương tác có thể tận dụng cơ chế lịch sử và DEBUGcái bẫy để "nói" các lệnh mà nó chạy dòng lệnh hoàn chỉnh mà chúng là một phần của biến môi trường:

$ trap 'export LC=$(fc -nl -0); LC=${LC#? }' DEBUG
$ sh -c 'printf "last_command={%s}\n" "$LC"' | cat; true
last_command={sh -c 'printf "last_command={%s}\n" "$LC"' | cat; true}

13

Không

bash (hoặc shell của bạn) sẽ rẽ nhánh hai lệnh riêng biệt.

  1. test.sh arg1
  2. grep "xyz"

test.sh không thể biết về việc theo grep.

tuy nhiên bạn có thể biết bạn đang "bên trong" một đường ống bằng cách thử nghiệm /proc/self/fd/1

kiểm tra

#!/bin/bash

file /proc/self/fd/1

mà chạy như

> ./test.sh
/proc/self/fd/1: symbolic link to /dev/pts/0
> ./test.sh | cat
/proc/self/fd/1: broken symbolic link to pipe:[25544239]

(Chỉnh sửa) xem nhận xét của muru về việc biết bạn đang ở trên đường ống.

bạn không cần phải biết nếu bạn đang ở trong một đường ống cho điều đó. Chỉ cần kiểm tra nếu đầu ra là một TTY. [ -t 1 ] https://unix.stackexchange.com/a/401938/70524


Mặc dù hữu ích, nhưng điều này chỉ hoạt động trên Linux - không phải các Unix khác
Scott Earle

2

Bằng cách sử dụng /proc/self/fd, bạn có thể xem liệu bạn đang ở trong một đường ống cũng như ID cho đường ống. Nếu bạn lặp đi lặp lại thông qua việc /proc/\*/fdtìm kiếm đường ống phù hợp, bạn có thể tìm thấy PID của đầu kia của ống. Với PID, sau đó bạn có thể đọc /proc/$PID/cmdlinecũng như lặp lại quy trình trên các bộ mô tả tệp của nó để tìm ra những gì nó được đưa vào.

$ cat | cat | cat &
$ ps
  PID TTY          TIME CMD
 6942 pts/16   00:00:00 cat
 6943 pts/16   00:00:00 cat
 6944 pts/16   00:00:00 cat
 7201 pts/16   00:00:00 ps
20925 pts/16   00:00:00 bash
$ ls -l /proc/6942/fd
lrwx------. 1 tim tim 64 Jul 24 19:59 0 -> /dev/pts/16
l-wx------. 1 tim tim 64 Jul 24 19:59 1 -> 'pipe:[49581130]'
lrwx------. 1 tim tim 64 Jul 24 19:59 2 -> /dev/pts/16
$ ls -l /proc/6943/fd
lr-x------. 1 tim tim 64 Jul 24 19:59 0 -> 'pipe:[49581130]'
l-wx------. 1 tim tim 64 Jul 24 19:59 1 -> 'pipe:[49581132]'
lrwx------. 1 tim tim 64 Jul 24 19:59 2 -> /dev/pts/16
$ ls -l /proc/6944/fd
lr-x------. 1 tim tim 64 Jul 24 19:59 0 -> 'pipe:[49581132]'
lrwx------. 1 tim tim 64 Jul 24 19:59 1 -> /dev/pts/16
lrwx------. 1 tim tim 64 Jul 24 19:59 2 -> /dev/pts/16

Ngoài ra nếu bạn may mắn, các lệnh khác nhau trong đường ống sẽ nhận được các PID liên tiếp, điều này sẽ giúp việc này dễ dàng hơn một chút.

Tôi thực sự không có một kịch bản để làm điều này, nhưng tôi đã chứng minh khái niệm này.


1

Một cách khác có thể là bằng cách truy cập $BASH_COMMANDbiến tự động, nhưng vốn dĩ nó rất dễ bay hơi và khó nắm bắt được giá trị mong muốn.

Tôi nghĩ rằng bạn chỉ có thể bắt nó chỉ thông qua một eval, điều này cũng liên quan đến việc gọi các dòng lệnh của bạn theo một cách đặc biệt, như trong:

CMD="${BASH_COMMAND##* eval }" eval './test.sh arg1 | grep "xyz"'

Ở đây $BASH_COMMANDđược mở rộng trong khi cũng thanh lọc nó lên đến evalbit của chuỗi và do đó chuỗi kết quả được "bẻ khóa" thành một $CMDbiến của trình trợ giúp .

Ví dụ nhỏ:

$ cat test.sh
#!/bin/sh

printf 'you are running %s\n' "$CMD"
sleep 1
echo bye bye
$
$ CMD="${BASH_COMMAND##* eval }" eval './test.sh | { grep -nH "."; }'
(standard input):1:you are running './test.sh | { grep -nH "."; }'
(standard input):2:bye bye
$

Đương nhiên nó cũng có thể làm việc (thực sự tốt hơn) trong khi cách gọi script bằng ví dụ sh -choặc bash -c, như trong:

$
$ CMD="${BASH_COMMAND}" sh -c './test.sh | { grep -nH "."; }'
(standard input):1:you are running CMD="${BASH_COMMAND}" sh -c './test.sh | { grep -nH "."; }'
(standard input):2:bye bye
$

Ở đây mà không thanh trừng các biến.


1

Cảm ơn câu trả lời của bạn. Tôi đã thử nghiệm những thứ khác nhau và đến với kịch bản thử nghiệm sau:

kiểm tra

hist=`fc -nl -0`
# remove leading and trailing whitespaces
hist="$(echo "${hist}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
echo "Command line from history: '$hist'"

if [ -t 1 ]; then
  echo "Direct output to TTY, no pipe involved."
else
  echo "No TTY, maybe a piped command."
fi

if [ -p /dev/stdout ]; then
  echo "stdout is a pipe."
else
  echo "stdout is not a pipe."
fi

readlink -e /proc/self/fd/1
rst=$?
if [ $rst -eq 0 ]; then
  echo "Readlink test status okay, no pipe involved."
else
  echo "Readlink test status error $rst, maybe a piped command."
fi

Các xét nghiệm:

$ ./test.sh test1
Command line from history: './test.sh test1'
Direct output to TTY, no pipe involved.
stdout is not a pipe.
/dev/pts/3
Readlink test status okay, no pipe involved.

$ ./test.sh test2 | cat
Command line from history: './test.sh test2 | cat'
No TTY, maybe a piped command.
stdout is a pipe.
Readlink test status error 1, maybe a piped command.

$ echo "another command before pipe doesn't matter" | ./test.sh test3
Command line from history: 'echo "another command before pipe doesn't matter" | ./test.sh test3'
Direct output to TTY, no pipe involved.
stdout is not a pipe.
/dev/pts/3
Readlink test status okay, no pipe involved.

Lịch sử dòng lệnh chỉ hoạt động mà không có Shebang ở dòng trên cùng của tập lệnh. Không biết điều này có hoạt động đáng tin cậy không và trên các hệ thống khác.

Tôi không thể chặn đầu ra từ "readlink" (hoặc "tệp" như được đề xuất từ ​​Archemar), khi trạng thái thành công ("/ dev / pts / 3"). Đầu ra đường ống đến / dev / null hoặc đến một biến sẽ dẫn đến trục trặc. Vì vậy, đây sẽ không phải là một lựa chọn cho tôi trong một kịch bản.

Kiểm tra TTY mà muru đề cập là dễ dàng và có thể đã đủ cho một số trường hợp sử dụng.

Chỉnh sửa: Tín dụng của tôi chuyển sang mosvy, bởi vì câu hỏi là làm thế nào để có được dòng lệnh hoàn chỉnh và không chỉ để xác định xem tập lệnh có nằm trên đường ống hay không. Tôi thích phần đơn giản "fc -nl -0" trong câu trả lời của anh ấy, bởi vì không cần cấu hình hệ thống nữa. Nó không phải là một giải pháp 100 phần trăm, nhưng điều này chỉ dành cho sử dụng cá nhân của tôi và do đó là đủ. Cảm ơn tất cả những người khác đã giúp đỡ của bạn.


Kiểm tra TTY cũng có thể được thực hiện cho stdin : [ -t 0 ]. Vì vậy, bạn có thể kiểm tra xem stdin hoặc stdout không phải là TTY và tiến hành tương ứng.
muru

Nếu bạn muốn biết thiết bị xuất chuẩn có phải là một ống không, trên Linux bạn có thể sử dụng if [ -p /dev/stdout ]; ...(giống như readlink /proc/self/fd/..điều này không hoạt động trên BSD).
mosvy

2
Kịch bản cần làm việc IMNSHO. các echo -egần như chắc chắn không muốn -e. Bạn cần nhiều trường hợp kiểm tra hơn, chuyển hướng đến một tệp, được gọi bên trong $(...). Tuy nhiên tôi sẽ khuyến khích bạn xem xét nếu đây là một ý tưởng tốt. Các chương trình như lsthay đổi đầu ra của chúng tùy thuộc vào việc chúng xuất ra một tty hay một đường ống gây khó chịu khi sử dụng.
icarus
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.