Câu hỏi Làm thế nào để thiết lập một biến cho đầu ra của một lệnh trong Bash?


Tôi có một kịch bản khá đơn giản, giống như sau:

#!/bin/bash

VAR1="$1"    
MOREF='sudo run command against $VAR1 | grep name | cut -c7-'

echo $MOREF

Khi tôi chạy kịch bản lệnh này từ dòng lệnh và truyền cho nó các đối số, tôi không nhận được bất kỳ đầu ra nào. Tuy nhiên, khi tôi chạy các lệnh chứa trong $MOREF biến, tôi có thể nhận được đầu ra.

Tôi muốn biết làm thế nào người ta có thể lấy kết quả của một lệnh mà cần phải được chạy trong một kịch bản, lưu nó vào một biến, và sau đó đầu ra biến trên màn hình?


1087
2018-01-10 20:58


gốc


Câu hỏi liên quan stackoverflow.com/questions/25116521/… - Sandeepan Nath
Là một biến số toàn bộ, được xác định bởi POSIX cho các tên biến có ý nghĩa với hệ điều hành hoặc trình bao chính nó, trong khi các tên có ít nhất một ký tự chữ thường được dành riêng cho việc sử dụng ứng dụng. Vì vậy, hãy xem xét sử dụng tên thường cho các biến shell của riêng bạn để tránh xung đột ngoài ý muốn (lưu ý rằng việc thiết lập một biến shell sẽ ghi đè lên bất kỳ biến môi trường nào có tên). - Charles Duffy
Là một sang một bên, hãy ghi lại đầu ra thành một biến chỉ để bạn có thể echo biến là một sử dụng vô dụng echo, và sử dụng vô số biến. - tripleee


Các câu trả lời:


Ngoài các backticks, bạn có thể sử dụng $(), mà tôi thấy dễ đọc hơn và cho phép làm tổ.

OUTPUT="$(ls -1)"
echo "${OUTPUT}"

Trích dẫn (") không quan trọng để bảo tồn các giá trị nhiều dòng.


1665
2018-01-10 21:04



Chúng tôi có thể cung cấp một số separator cho đa dòng đầu ra? - Aryan
FYI Điều này được gọi là "thay thế lệnh": gnu.org/software/bash/manual/bashref.html#Command-Substitution - David Doria
Không gian trắng (hoặc thiếu khoảng trống) - Ali
@ timhc22, các dấu ngoặc nhọn không liên quan; nó chỉ là những dấu ngoặc kép quan trọng: liệu các kết quả mở rộng có phân chia chuỗi và được mở rộng bằng glob hay không trước khi được chuyển tới echochỉ huy. - Charles Duffy
Niềng răng xoăn có thể được sử dụng khi biến được ngay lập tức theo sau bởi nhiều ký tự có thể được hiểu là một phần của tên biến. ví dụ.  ${OUTPUT}foo. Chúng cũng được yêu cầu khi thực hiện các hoạt động chuỗi nội tuyến trên biến, chẳng hạn như ${OUTPUT/foo/bar} - rich remer


Cập nhật (2018): đúng cách là

$(sudo run command)

Bạn đang sử dụng sai loại dấu nháy đơn. Bạn cần `, không phải '. Ký tự này được gọi là "backticks" (hoặc "dấu nhấn").

Như thế này:

#!/bin/bash

VAR1="$1"
VAR2="$2"

MOREF=`sudo run command against "$VAR1" | grep name | cut -c7-`

echo "$MOREF"

201
2018-01-10 21:00



Cú pháp backtick là lỗi thời, và bạn thực sự cần đặt dấu ngoặc kép xung quanh nội suy biến trong echo. - tripleee
Tôi sẽ thêm rằng bạn phải cẩn thận với các khoảng trống xung quanh '=' trong bài tập ở trên. Bạn shouln't có bất kỳ không gian ở đó, nếu không bạn sẽ nhận được một bài tập sai - Zotov
nhận xét của baeee là chính xác. Trong cygwin (tháng 5 năm 2016), `` không hoạt động trong khi $() công trinh. Không thể sửa cho đến khi tôi thấy trang này. - toddwz
Xây dựng như một ví dụ về Cập nhật (2018) sẽ được đánh giá cao. - Eduard


Như họ đã chỉ ra cho bạn, bạn nên sử dụng 'backticks'.

Đề xuất thay thế $(command) cũng hoạt động tốt và dễ đọc hơn, nhưng lưu ý rằng nó chỉ hợp lệ với các shell bash hoặc korn (và các shell xuất phát từ các shell), do đó, nếu kịch bản của bạn phải thực sự di động trên các hệ thống Unix khác nhau, bạn nên sử dụng ký hiệu backticks cũ hơn.


57
2018-01-11 22:14



Họ quá thận trọng. Backticks đã bị từ chối bởi POSIX một thời gian dài trước đây; cú pháp hiện đại hơn nên có sẵn trong hầu hết các vỏ từ thiên niên kỷ này. (Vẫn còn môi trường cũ hoHP-UXho bị mắc kẹt vững chắc vào đầu những năm chín mươi.) - tripleee
Sai. $() hoàn toàn tương thích với POSIX sh, được chuẩn hóa hơn hai thập kỷ trước. - Charles Duffy
Lưu ý rằng /bin/sh trên Solaris 10 vẫn không nhận ra $(…) - và AFAIK cũng đúng trên Solaris 11. - Jonathan Leffler
@JonathanLeffler Nó thực sự là không có nhiều trường hợp với Solaris 11, nơi /bin/sh Là ksh93. - jlliagre
@tripleee - phản hồi ba năm muộn :-) nhưng tôi đã sử dụng $() trong vỏ POSIX trên HP-UX trong 10 năm qua. - Bob Jarvis


Tôi biết ba cách để làm:

1) Chức năng phù hợp cho các nhiệm vụ như sau:

func (){
ls -l
}

Gọi nó bằng cách nói func

2) Một giải pháp phù hợp khác có thể là eval:

var="ls -l"
eval $var

3) Thứ ba là sử dụng các biến trực tiếp:

var=$(ls -l)
OR
var=`ls -l`

bạn có thể nhận được đầu ra của giải pháp thứ ba theo cách tốt:

echo "$var"

và cũng theo cách khó chịu:

echo $var

45
2018-02-13 07:31



Hai người đầu tiên dường như không trả lời câu hỏi vì nó hiện đang đứng, và thứ hai thường được coi là đáng ngờ. - tripleee
Là một người hoàn toàn mới bash, tại sao "$var" tốt và $var bẩn thỉu? - Peter
@Peter stackoverflow.com/questions/10067266/… - tripleee


Một số  thủ thuật tôi sử dụng để đặt biến từ lệnh

2nd Chỉnh sửa 2018-02-12: Thêm một cách đặc biệt, xem ở dưới cùng của điều này!

2018-01-25 Chỉnh sửa: thêm chức năng mẫu (để điền các vars về cách sử dụng đĩa)

Cách đơn giản và tương thích đầu tiên

myPi=`echo '4*a(1)' | bc -l`
echo $myPi 
3.14159265358979323844

Chủ yếu là tương thích, thứ hai

Khi lồng nhau có thể trở nên nặng nề, dấu ngoặc đơn đã được thực hiện cho điều này

myPi=$(bc -l <<<'4*a(1)')

Mẫu lồng nhau:

SysStarted=$(date -d "$(ps ho lstart 1)" +%s)
echo $SysStarted 
1480656334

đọc nhiều hơn một biến (với bashisms)

df -k /
Filesystem     1K-blocks   Used Available Use% Mounted on
/dev/dm-0         999320 529020    401488  57% /

Nếu tôi chỉ muốn Đã sử dụng giá trị:

array=($(df -k /))

bạn có thể nhìn thấy mảng biến:

declare -p array
declare -a array='([0]="Filesystem" [1]="1K-blocks" [2]="Used" [3]="Available" [
4]="Use%" [5]="Mounted" [6]="on" [7]="/dev/dm-0" [8]="999320" [9]="529020" [10]=
"401488" [11]="57%" [12]="/")'

Sau đó:

echo ${array[9]}
529020

Nhưng tôi thích điều này:

{ read foo ; read filesystem size used avail prct mountpoint ; } < <(df -k /)
echo $used
529020

Ngày 1 read foo Sẽ chỉ bỏ qua dòng tiêu đề (biến $foo sẽ chứa một cái gì đó như Filesystem 1K-blocks Used Available Use% Mounted on)

Hàm mẫu để điền một số biến:

#!/bin/bash

declare free=0 total=0 used=0

getDiskStat() {
    local foo
    {
        read foo
        read foo total used free foo
    } < <(
        df -k ${1:-/}
    )
}

getDiskStat $1
echo $total $used $free

Nota: declare dòng là không cần thiết, chỉ để dễ đọc.

Trong khoảng sudo cmd | grep ... | cut ...

shell=$(cat /etc/passwd | grep $USER | cut -d : -f 7)
echo $shell
/bin/bash

(Xin vui lòng tránh vô ích cat! Vì vậy, đây chỉ là 1 ngã ba ít hơn:

shell=$(grep $USER </etc/passwd | cut -d : -f 7)

Tất cả các đường ống (|) ngụ ý dĩa. Trường hợp một tiến trình khác phải được chạy, truy cập đĩa, các cuộc gọi thư viện và vv.

Vì vậy, sử dụng sed đối với mẫu, sẽ giới hạn quy trình con cho chỉ một cái nĩa:

shell=$(sed </etc/passwd "s/^$USER:.*://p;d")
echo $shell

Và với bashisms:

Nhưng đối với nhiều hành động, chủ yếu là trên các tệp nhỏ,  có thể tự làm công việc:

while IFS=: read -a line ; do
    [ "$line" = "$USER" ] && shell=${line[6]}
  done </etc/passwd
echo $shell
/bin/bash

hoặc là

while IFS=: read loginname encpass uid gid fullname home shell;do
    [ "$loginname" = "$USER" ] && break
  done </etc/passwd
echo $shell $loginname ...

Đi xa hơn về -sự tách biến đổi...

Hãy xem câu trả lời của tôi Làm thế nào để tách một chuỗi trên một dấu phân tách trong Bash?

Thay thế: giảm dĩa bằng cách sử dụng các tác vụ chạy trong nền dài

Lần sửa thứ hai 2018-02-12: Để ngăn chặn nhiều nhánh như

myPi=$(bc -l <<<'4*a(1)'
myRay=12
myCirc=$(bc -l <<<" 2 * $myPi * $myRay ")

hoặc là

myStarted=$(date -d "$(ps ho lstart 1)" +%s)
mySessStart=$(date -d "$(ps ho lstart $$)" +%s)

Và bởi vì date và bc có thể làm việc từng dòng:

bc -l <<<$'3*4\n5*6'
12
30

date -f - +%s < <(ps ho lstart 1 $$)
1516030449
1517853288

Chúng ta có thể sử dụng chạy dài quá trình nền để thực hiện công việc lặp đi lặp lại, mà không cần phải bắt đầu công việc mới cái nĩa cho mỗi yêu cầu:

mkfifo /tmp/myFifoForBc
exec 5> >(bc -l >/tmp/myFifoForBc)
exec 6</tmp/myFifoForBc
rm /tmp/myFifoForBc

(tất nhiên, FD 5 và 6 phải được sử dụng!) ... Từ đó, bạn có thể sử dụng quy trình này bằng cách:

echo "3*4" >&5
read -u 6 foo
echo $foo
12

echo >&5 "pi=4*a(1)"
echo >&5 "2*pi*12"
read -u 6 foo
echo $foo
75.39822368615503772256

Vào một hàm newConnector

Bạn có thể tìm thấy newConnector chức năng trên GitHub.Com hoặc trên trang web của riêng tôi (Nota trên github, có hai tệp, trên trang web, chức năng và bản trình diễn của tôi được nhóm thành 1 tệp có thể được cung cấp để sử dụng hoặc chỉ chạy để chạy thử)

Mẫu vật:

. shell_connector.sh

tty
/dev/pts/20

ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
  29019 pts/20   Ss     0:00 bash
  30745 pts/20   R+     0:00  \_ ps --tty pts/20 fw

newConnector /usr/bin/bc "-l" '3*4' 12

ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
  29019 pts/20   Ss     0:00 bash
  30944 pts/20   S      0:00  \_ /usr/bin/bc -l
  30952 pts/20   R+     0:00  \_ ps --tty pts/20 fw

declare -p PI
bash: declare: PI: not found

myBc '4*a(1)' PI
declare -p PI
declare -- PI="3.14159265358979323844"

Chức năng myBc cho phép bạn sử dụng tác vụ nền với cú pháp đơn giản và cho đến ngày:

newConnector /bin/date '-f - +%s' @0 0
myDate '2000-01-01'
  946681200
myDate "$(ps ho lstart 1)" boottime
myDate now now ; read utm idl </proc/uptime
myBc "$now-$boottime" uptime
printf "%s\n" ${utm%%.*} $uptime
  42134906
  42134906

ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
  29019 pts/20   Ss     0:00 bash
  30944 pts/20   S      0:00  \_ /usr/bin/bc -l
  32615 pts/20   S      0:00  \_ /bin/date -f - +%s
   3162 pts/20   R+     0:00  \_ ps --tty pts/20 fw

Từ đó, nếu bạn muốn kết thúc một quá trình nền, bạn chỉ cần đóng fd:

eval "exec $DATEOUT>&-"
eval "exec $DATEIN>&-"
ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
   4936 pts/20   Ss     0:00 bash
   5256 pts/20   S      0:00  \_ /usr/bin/bc -l
   6358 pts/20   R+     0:00  \_ ps --tty pts/20 fw

đó là không cần thiết, bởi vì tất cả fd đóng khi kết thúc quá trình chính.


36
2017-12-20 07:06



Mẫu lồng nhau ở trên là những gì tôi đang tìm kiếm. Có thể có một cách đơn giản hơn, nhưng những gì tôi đang tìm kiếm là cách để tìm hiểu xem một container docker đã tồn tại cho tên của nó trong một biến môi trường. Vì vậy, đối với tôi: EXISTING_CONTAINER=$(docker ps -a | grep "$(echo $CONTAINER_NAME)") là tuyên bố mà tôi đang tìm kiếm. - Capricorn1
@ capricorn1 Đó là một sử dụng vô dụng echo; bạn muốn đơn giản grep "$CONTAINER_NAME" - tripleee
2018 Chỉnh sửa: Thêm chức năng mẫu (để điền các vars về cách sử dụng đĩa) - F. Hauri


Chỉ cần khác:

MOREF=$(sudo run command against $VAR1 | grep name | cut -c7-)

22
2018-01-10 21:07





Nếu bạn muốn làm điều đó với multiline / multiple command / s thì bạn có thể làm điều này:

output=$( bash <<EOF
#multiline/multiple command/s
EOF
)

Hoặc là:

output=$(
#multiline/multiple command/s
)

Thí dụ:

#!/bin/bash
output="$( bash <<EOF
echo first
echo second
echo third
EOF
)"
echo "$output"

Đầu ra:

first
second
third

Sử dụng heredoc bạn có thể đơn giản hóa mọi thứ khá dễ dàng bằng cách chia nhỏ mã dòng đơn dài của bạn thành mã đa dòng. Một vi dụ khac:

output="$( ssh -p $port $user@$domain <<EOF 
#breakdown your long ssh command into multiline here.
EOF
)"

12
2018-06-01 17:38



Cái gì với cái thứ hai bash bên trong lệnh thay thế? Bạn đã tạo ra một subshell bằng lệnh thay thế chính nó. Nếu bạn muốn đặt nhiều lệnh, chỉ cần tách chúng bằng dòng mới hoặc dấu chấm phẩy. output=$(echo first; echo second; ...) - tripleee
@tripleee chỉ là những cách khác nhau để hoàn thành nó. - Jahid
Sau đó tương tự 'bash -c "bash -c \"bash -c ...\""' cũng sẽ là "khác"; nhưng tôi không thấy điểm đó. - tripleee
Tôi không cảm thấy chúng ta đang giao tiếp đúng cách. Tôi đang thách thức tính hữu dụng hơn variable=$(bash -c 'echo "foo"; echo "bar"') kết thúc variable=$(echo "foo"; echo "bar") - tài liệu ở đây chỉ là một cơ chế trích dẫn và không thực sự thêm bất cứ điều gì ngoại trừ một biến chứng vô dụng khác. - tripleee
Khi tôi sử dụng heredoc với ssh, tôi chính xác lệnh để chạy ssh -p $port $user@$domain /bin/bash <<EOF để ngăn chặn Pseudo-terminal will not be allocated because stdin is not a terminal.cảnh báo - F. Hauri


Khi đặt biến, hãy đảm bảo bạn có Không có khoảng trắng trước và / hoặc sau = ký tên. Nghĩa đen đã dành một giờ cố gắng để tìm ra điều này, cố gắng tất cả các loại giải pháp! Nó không tuyệt.

Chính xác:

WTFF=`echo "stuff"`
echo "Example: $WTFF"

Sẽ thất bại với lỗi: (công cụ: không tìm thấy hoặc tương tự)

WTFF= `echo "stuff"`
echo "Example: $WTFF"

10
2017-07-18 11:42



Tôi đã không nhận thấy điều này trước đó. Cảm ơn :) - Ayyappa


Bạn có thể sử dụng back-ve (còn được gọi là ngôi mộ giọng) hoặc $(). Giống như là-

OUTPUT=$(x+2);
OUTPUT=`x+2`;

Cả hai đều có tác dụng tương tự. Nhưng OUTPUT = $ (x + 2) dễ đọc hơn và mới nhất.


5
2018-02-09 08:03



bash: x+2: command not found - F. Hauri
Dấu ngoặc đơn đã được thực hiện để cho phép làm tổ. - F. Hauri


Đây là một cách khác, tốt để sử dụng với một số trình chỉnh sửa văn bản không thể đánh dấu chính xác mọi mã phức tạp mà bạn tạo.

read -r -d '' str < <(cat somefile.txt)
echo "${#str}"
echo "$str"

3
2018-05-10 21:44



Điều này không giải quyết được câu hỏi của OP, mà thực sự là về thay thế lệnh, không phải quá trình thay thế. - codeforester
@codeforester nhưng đã làm việc này cho bạn quá? - Aquarius Power