it-swarm-vi.com

Làm cách nào để đặt biến cho đầu ra của 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 tập 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 có trong biến $MOREF, tôi có thể nhận được đầu ra.

Làm thế nào người ta có thể lấy kết quả của một lệnh cần được chạy trong tập lệnh, lưu nó vào một biến và sau đó xuất biến đó trên màn hình?

1317
John

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

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

MULTILINE=$(ls \
   -1)
echo "${MULTILINE}"

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

1989
Andy Lester

Cách đúng là

$(Sudo run command)

Nếu bạn định sử dụng dấu nháy đơn, bạn cần `, không phải '. Nhân vật này được gọi là "backticks" (hay "Grave accent").

Như thế này:

#!/bin/bash

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

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

echo "$MOREF"
240
Ilya Kogan

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

Giải pháp thay thế được đề xuất $(command) cũng hoạt động và cũng dễ đọc hơn, nhưng lưu ý rằng nó chỉ hợp lệ với Bash hoặc KornShell (và các shell có nguồn gốc từ chúng), vì vậy nếu tập lệnh 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 thích các ký hiệu backticks cũ.

64
bitwelder

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

Chỉnh sửa lần 2 2018-02-12: Thêm một cách khác, tìm kiếm ở dưới cùng của mục này cho tác vụ dài hạn!

2018-01-25 Chỉnh sửa: thêm chức năng mẫu (để điền vào các vars về việc 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

Hầu hết tương thích, cách thứ hai

Khi lồng 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 bashism)

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

Nếu tôi chỉ muốn Được sử dụng value:

array=($(df -k /))

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

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

read foo thứ nhất 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: Không yêu cầu dòng declare, chỉ để dễ đọc.

Giới thiệu về Sudo cmd | grep ... | cut ...

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

(Vui lòng tránh vô dụng cat! Vì vậy, đây chỉ là 1 ngã ba:

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

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

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

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

Và với bashism:

Nhưng đối với nhiều hành động, chủ yếu là trên các tệp nhỏ, bash có thể tự thực hiện 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ề chia tách biến...

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

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

Chỉnh sửa lần 2 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)

Điều này làm việc tốt, nhưng chạy nhiều dĩa là nặng và chậm.

và các lệnh như datebc có thể thực hiện nhiều thao tác, theo từng dòng !!

Xem:

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

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

Vì vậy, chúng tôi có thể sử dụng chạy dài quá trình nền để thực hiện nhiều công việc, mà không phải bắt đầu fork mới cho mỗi yêu cầu.

Chúng tôi chỉ cần một số mô tả tệpfifos để thực hiện việc này đúng cách:

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

(tất nhiên, FD 56 phải không đượ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 hàm newConnector của tôi 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 của tôi, chức năng và bản demo được gói thành 1 tệp có thể được lấy nguồn từ 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"

Hàm myBc cho phép bạn sử dụng tác vụ nền với cú pháp đơn giản và cho 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

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

59
F. Hauri

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

1) Các chức năng phù hợp cho các nhiệm vụ đó:

func (){
ls -l
}

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

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

var="ls -l"
eval $var

3) Cái thứ ba đang 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
52
MLSC

Chỉ để khác biệt:

MOREF=$(Sudo run command against $VAR1 | grep name | cut -c7-)
24
DigitalRoss

Nếu bạn muốn làm điều đó với multiline/nhiều lệnh/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 thành nhiều dòng. Một vi dụ khac:

output="$( ssh -p $port [email protected]$domain <<EOF 
#breakdown your long ssh command into multiline here.
EOF
)"
13
Jahid

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

Đúng:

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

Sẽ thất bại có lỗi: (nội dung: không tìm thấy hoặc tương tự)

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

Bạn cần sử dụng một trong hai

$(command-here)

hoặc là

`command-here`

thí dụ

#!/bin/bash

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

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

echo "$MOREF"
8
Diego Velez

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

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

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

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

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

6
Pratik Patil

Một số có thể tìm thấy điều này hữu ích. Các giá trị số nguyên thay thế cho biến, trong đó thủ thuật đang sử dụng dấu ngoặc kép $(()):

N=3
M=3
COUNT=$N-1
ARR[0]=3
ARR[1]=2
ARR[2]=4
ARR[3]=1

while (( COUNT < ${#ARR[@]} ))
do
  ARR[$COUNT]=$((ARR[COUNT]*M))
  (( COUNT=$COUNT+$N ))
done
4
Gus

Đây là hai cách khác:

Xin lưu ý rằng không gian rất quan trọng trong bash . Vì vậy, nếu bạn muốn lệnh của mình chạy, hãy sử dụng như không có thêm bất kỳ khoảng trắng nào.

  1. sau đây gán khắc nghiệt cho L và sau đó in nó

    L=$"harshil"
    echo "$L"
    
  2. sau đây gán đầu ra của lệnh tr cho L2. tr đang được vận hành trên một biến L1 khác.

    L2=$(echo "$L1" | tr [:upper:] [:lower:])
    
4
Harshil

Nếu lệnh mà bạn đang cố thực hiện không thành công, nó sẽ ghi đầu ra vào luồng lỗi và sau đó sẽ được in ra bàn điều khiển. Để tránh nó, bạn phải chuyển hướng luồng lỗi

result=$(ls -l something_that_does_not_exist 2>&1)
1
cafebabe1991