Làm thế nào để lưu trữ một lệnh trong một biến trong tập lệnh shell?


113

Tôi muốn lưu trữ một lệnh để sử dụng sau này trong một biến (không phải đầu ra của lệnh, mà là chính lệnh đó)

Tôi có một tập lệnh đơn giản như sau:

command="ls";
echo "Command: $command"; #Output is: Command: ls

b=`$command`;
echo $b; #Output is: public_html REV test... (command worked successfully)

Tuy nhiên, khi tôi thử một cái gì đó phức tạp hơn một chút, nó không thành công. Ví dụ, nếu tôi làm

command="ls | grep -c '^'";

Đầu ra là:

Command: ls | grep -c '^'
ls: cannot access |: No such file or directory
ls: cannot access grep: No such file or directory
ls: cannot access '^': No such file or directory

Bất kỳ ý tưởng nào về cách tôi có thể lưu trữ một lệnh như vậy (với các đường ống / nhiều lệnh) trong một biến để sử dụng sau này?


10
Sử dụng một chức năng!
gniourf_gniourf

Câu trả lời:


146

Sử dụng eval:

x="ls | wc"
eval "$x"
y=$(eval "$x")
echo "$y"

27
$ (...) bây giờ được đề xuất thay vì backticks. y = $ (eval $ x) mywiki.wooledge.org/BashFAQ/082
James Broadhead

14
evallà một phương pháp được chấp nhận chỉ khi bạn tin tưởng vào nội dung của các biến. Nếu bạn đang chạy, chẳng hạn như x="ls $name | wc"(hoặc thậm chí x="ls '$name' | wc"), thì mã này là một dấu vết nhanh chóng để đưa vào hoặc các lỗ hổng leo thang đặc quyền nếu biến đó có thể được đặt bởi người có ít đặc quyền hơn. ( /tmpVí dụ: lặp đi lặp lại trên tất cả các thư mục con trong ? Bạn nên tin tưởng mọi người dùng đơn lẻ trên hệ thống để không thực hiện một cuộc gọi $'/tmp/evil-$(rm -rf $HOME)\'$(rm -rf $HOME)\'/').
Charles Duffy

9
evallà một nam châm lỗi khổng lồ không bao giờ được khuyến nghị mà không có cảnh báo về nguy cơ xảy ra hành vi phân tích cú pháp không mong muốn (ngay cả khi không có chuỗi độc hại, như trong ví dụ của @ CharlesDuffy). Ví dụ, hãy thử x='echo $(( 6 * 7 ))'và sau đó eval $x. Bạn có thể mong đợi điều đó để in "42", nhưng nó có thể sẽ không. Bạn có thể giải thích tại sao nó không hoạt động? Bạn có thể giải thích tại sao tôi nói "có lẽ"? Nếu câu trả lời cho những câu hỏi đó không rõ ràng đối với bạn, bạn đừng bao giờ chạm vào eval.
Gordon Davisson

1
@Student, hãy thử chạy set -xtrước để ghi lại các lệnh chạy, điều này sẽ giúp bạn dễ dàng xem những gì đang xảy ra.
Charles Duffy

1
@Student Tôi cũng muốn giới thiệu shellcheck.net để chỉ ra những lỗi phổ biến (và những thói quen xấu bạn không nên mắc phải).
Gordon Davisson

41

Đừng không sử dụng eval! Nó có một rủi ro lớn là thực thi mã tùy ý.

BashFAQ-50 - Tôi đang cố đặt lệnh vào một biến, nhưng các trường hợp phức tạp luôn không thành công.

Đặt nó trong một mảng và mở rộng tất cả các từ có dấu ngoặc kép "${arr[@]}"để không để xảy IFSra hiện tượng tách từ do Tách từ .

cmdArgs=()
cmdArgs=('date' '+%H:%M:%S')

và xem nội dung của mảng bên trong. Các declare -pphép bạn xem nội dung của các bên trong mảng với mỗi tham số lệnh trong chỉ số riêng biệt. Nếu một đối số như vậy chứa khoảng trắng, trích dẫn bên trong khi thêm vào mảng sẽ ngăn nó bị tách do Tách từ.

declare -p cmdArgs
declare -a cmdArgs='([0]="date" [1]="+%H:%M:%S")'

và thực hiện các lệnh như

"${cmdArgs[@]}"
23:15:18

(hoặc) hoàn toàn sử dụng một bashhàm để chạy lệnh,

cmd() {
   date '+%H:%M:%S'
}

và gọi hàm chỉ là

cmd

POSIX shkhông có mảng, vì vậy cách gần nhất bạn có thể đến là tạo danh sách các phần tử trong các tham số vị trí. Đây là một shcách POSIX để chạy chương trình thư

# POSIX sh
# Usage: sendto subject address [address ...]
sendto() {
    subject=$1
    shift
    first=1
    for addr; do
        if [ "$first" = 1 ]; then set --; first=0; fi
        set -- "$@" --recipient="$addr"
    done
    if [ "$first" = 1 ]; then
        echo "usage: sendto subject address [address ...]"
        return 1
    fi
    MailTool --subject="$subject" "$@"
}

Lưu ý rằng cách tiếp cận này chỉ có thể xử lý các lệnh đơn giản mà không có chuyển hướng. Nó không thể xử lý chuyển hướng, đường ống, vòng lặp for / while, câu lệnh if, v.v.

Một trường hợp sử dụng phổ biến khác là khi chạy curlvới nhiều trường tiêu đề và tải trọng. Bạn luôn có thể xác định các args như bên dưới và gọi curltrên nội dung mảng được mở rộng

curlArgs=('-H' "keyheader: value" '-H' "2ndkeyheader: 2ndvalue")
curl "${curlArgs[@]}"

Một vi dụ khac,

payload='{}'
hostURL='http://google.com'
authToken='someToken'
authHeader='Authorization:Bearer "'"$authToken"'"'

bây giờ các biến đã được xác định, hãy sử dụng một mảng để lưu trữ các args lệnh của bạn

curlCMD=(-X POST "$hostURL" --data "$payload" -H "Content-Type:application/json" -H "$authHeader")

và bây giờ thực hiện mở rộng được trích dẫn thích hợp

curl "${curlCMD[@]}"

Điều này không hiệu quả với tôi, tôi đã thử Command=('echo aaa | grep a')"${Command[@]}"hy vọng nó chạy lệnh theo đúng nghĩa đen echo aaa | grep a. Nó không. Tôi tự hỏi liệu có cách nào an toàn để thay thế không eval, nhưng có vẻ như mỗi giải pháp có lực tác động như nhau đều evalcó thể nguy hiểm. Phải không?
Sinh viên

Tóm lại, điều này hoạt động như thế nào nếu chuỗi ban đầu chứa một ký tự '|'?
Sinh viên

@Student, nếu chuỗi ban đầu của bạn chứa một đường ống, thì chuỗi đó cần phải đi qua các phần không an toàn của trình phân tích cú pháp bash để được thực thi dưới dạng mã. Không sử dụng một chuỗi trong trường hợp đó; thay vào đó hãy sử dụng một hàm: Command() { echo aaa | grep a; }- sau đó bạn có thể chạy Command, hoặc result=$(Command), hoặc tương tự.
Charles Duffy

1
@ Học sinh, đúng; nhưng điều đó cố ý không thành công , bởi vì những gì bạn đang yêu cầu làm vốn dĩ không an toàn .
Charles Duffy

1
@Student: Tôi đã thêm một ghi chú cuối cùng để đề cập rằng nó không hoạt động trong một số điều kiện nhất định
Inian

25
var=$(echo "asdf")
echo $var
# => asdf

Sử dụng phương pháp này, lệnh được đánh giá ngay lập tức và giá trị trả về của nó được lưu trữ.

stored_date=$(date)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015

Tương tự với backtick

stored_date=`date`
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015

Sử dụng eval trong $(...)sẽ không làm cho nó được đánh giá sau này

stored_date=$(eval "date")
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015

Sử dụng eval, nó được đánh giá khi evalđược sử dụng

stored_date="date" # < storing the command itself
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:05 EST 2015
# (wait a few seconds)
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:16 EST 2015
#                     ^^ Time changed

Trong ví dụ trên, nếu bạn cần chạy một lệnh với các đối số, hãy đặt chúng vào chuỗi bạn đang lưu trữ

stored_date="date -u"
# ...

Đối với các tập lệnh bash, điều này hiếm khi có liên quan, nhưng một lưu ý cuối cùng. Hãy cẩn thận với eval. Chỉ đánh giá các chuỗi mà bạn kiểm soát, không bao giờ đánh giá các chuỗi đến từ người dùng không đáng tin cậy hoặc được tạo từ đầu vào của người dùng không đáng tin cậy.

  • Cảm ơn @CharlesDuffy đã nhắc tôi trích dẫn câu lệnh!

Điều này không giải quyết được vấn đề ban đầu trong đó lệnh chứa một ký tự '|'.
Sinh viên

@Nate, lưu ý rằng eval $stored_datecó thể đủ tốt khi stored_datechỉ chứa date, nhưng eval "$stored_date"đáng tin cậy hơn nhiều. Chạy str=$'printf \' * %s\\n\' *'; eval "$str"có và không có dấu ngoặc kép xung quanh phần cuối cùng "$str"để làm ví dụ. :)
Charles Duffy

@CharlesDuffy Cảm ơn, tôi đã quên việc trích dẫn. Tôi cá rằng chiếc linter của tôi sẽ phàn nàn nếu tôi bận tâm đến việc chạy nó.
Nate

0

Đối với bash, hãy lưu trữ lệnh của bạn như sau:

command="ls | grep -c '^'"

Chạy lệnh của bạn như sau:

echo $command | bash

1
Không chắc chắn nhưng có lẽ cách chạy lệnh này có những rủi ro giống như việc sử dụng 'eval'.
Derek Hazell

0

Tôi đã thử nhiều phương pháp khác nhau:

printexec() {
  printf -- "\033[1;37m$\033[0m"
  printf -- " %q" "$@"
  printf -- "\n"
  eval -- "$@"
  eval -- "$*"
  "$@"
  "$*"
}

Đầu ra:

$ printexec echo  -e "foo\n" bar
$ echo -e foo\\n bar
foon bar
foon bar
foo
 bar
bash: echo -e foo\n bar: command not found

Như bạn có thể thấy, chỉ có kết quả thứ ba "$@"cho kết quả chính xác.


0

Hãy cẩn thận đăng ký một đơn đặt hàng với: X=$(Command)

Cái này vẫn được thực thi Ngay cả trước khi được gọi. Để kiểm tra và xác nhận điều này, bạn thực hiện:

echo test;
X=$(for ((c=0; c<=5; c++)); do
sleep 2;
done);
echo note the 5 seconds elapsed

-1
#!/bin/bash
#Note: this script works only when u use Bash. So, don't remove the first line.

TUNECOUNT=$(ifconfig |grep -c -o tune0) #Some command with "Grep".
echo $TUNECOUNT                         #This will return 0 
                                    #if you don't have tune0 interface.
                                    #Or count of installed tune0 interfaces.

-8

Nó không cần thiết phải lưu trữ các lệnh trong các biến ngay cả khi bạn cần sử dụng nó sau này. chỉ cần thực hiện nó như bình thường. Nếu bạn lưu trữ trong biến, bạn sẽ cần một số loại evalcâu lệnh hoặc gọi một số quy trình shell không cần thiết để "thực thi biến của bạn".


1
Lệnh tôi sẽ lưu trữ sẽ phụ thuộc vào các tùy chọn tôi gửi vào, vì vậy thay vì có hàng tấn câu lệnh điều kiện trong chương trình của tôi, việc lưu trữ lệnh tôi cần để sử dụng sau này dễ dàng hơn rất nhiều.
Benjamin

1
@Benjamin, thì ít nhất hãy lưu trữ các tùy chọn dưới dạng biến chứ không phải lệnh. ví dụvar='*.txt'; find . -name "$var"
kurumi
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.