Як встановити змінну на вихід команди в Bash?


1675

У мене досить простий сценарій, який виглядає приблизно так:

#!/bin/bash

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

echo $MOREF

Коли я запускаю цей скрипт з командного рядка і передаю йому аргументи, я не отримую жодного результату. Однак, коли я запускаю команди, що містяться в $MOREFзмінній, я можу отримати вихід.

Як можна взяти результати команди, яку потрібно запустити в сценарії, зберегти її до змінної та вивести цю змінну на екран?


1
Питання, пов’язані з цим stackoverflow.com/questions/25116521/…
Sandeepan Nath,

40
На відміну від POSIX -змінних визначаються змінні з усіма величинами для імен змінних, що мають значення для самої операційної системи або оболонки, тоді як імена, що мають щонайменше один нижній регістр, зарезервовані для використання додатків. Таким чином, розгляньте можливість використання малих імен для власних змінних оболонок, щоб уникнути ненавмисних конфліктів (маючи на увазі, що встановлення змінної оболонки замінить будь-яку змінну оточуючого середовища на зразок).
Чарльз Даффі

1
Як в сторону, захоплюючи висновок в змінну тільки таким чином ви можете потім echoзмінна є непотрібним використання echo, і марно використання змінних.
трійка

1
Крім того, зберігання результатів у змінних часто непотрібне. Для невеликих коротких рядків вам потрібно буде кілька разів посилатися у вашій програмі, це абсолютно добре, і саме шлях; але для обробки будь-якої нетривіальної кількості даних ви хочете змінити свій процес у конвеєр або використати тимчасовий файл.
трійка

Відповіді:


2375

Окрім зворотних посилань `command`, підміна команд може бути виконана з $(command)чи "$(command)", що мені здається легше читати, і дозволяє вкладати.

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

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

Цитування ( ") має значення для збереження багаторядкових змінних значень ; це необов’язково в правій частині завдання, оскільки розбиття слів не виконується , тож OUTPUT=$(ls -1)буде добре працювати.


59
Чи можемо ми надати якийсь роздільник для багаторядкового виводу?
Арій

20
Білий простір (або відсутність пробілу) має значення
Алі

8
@ timhc22, фігурні брекети не мають значення; важливі лише цитати: чи розбиті результати розширення та розширюються на глобальні, перш ніж передаватися echoкоманді.
Чарльз Даффі

4
Ах, дякую! То чи є користь фігурним брекетам?
timhc22

14
Фігурні дужки можна використовувати, коли за змінною одразу слідує більше символів, які можна інтерпретувати як частину назви змінної. напр ${OUTPUT}foo . Вони також потрібні під час виконання вбудованих рядкових операцій зі змінною, наприклад${OUTPUT/foo/bar}
багатий ремер

282

Правильний шлях

$(sudo run command)

Якщо ви збираєтесь використовувати апостроф, вам потрібно `, ні '. Цей символ називається "backticks" (або "серйозний акцент").

Подобається це:

#!/bin/bash

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

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

echo "$MOREF"

31
Синтаксис backtick застарілий, і вам дійсно потрібно поставити подвійні лапки навколо змінної інтерполяції в echo.
трійка

10
Я додам, що ви повинні бути обережними з пробілами навколо '=' у призначенні вище. У вас там не залишиться пробілів , інакше ви отримаєте неправильне завдання
zbstof

4
коментар tripleeee правильний. У cygwin (травень 2016 р.) `` Не працює, поки $()працює. Не вдалося виправити, поки я не побачив цю сторінку.
toddwz

2
Оцінка такої розробки, як приклад оновлення (2018) .
Едуард

90

Деякі хитрості Баша, які я використовую для встановлення змінних з команд

2-е редагування 2018-02-12: Додано інший спосіб, шукайте внизу цього довгострокові завдання !

2018-01-25 Редагувати: додана вибіркова функція (для заповнення змінних про використання диска)

Перший простий, старий і сумісний спосіб

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

Переважно сумісний, другий спосіб

Оскільки гніздування може стати важким, для цього було застосовано круглі дужки

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

Вкладений зразок:

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

Читання більш ніж однієї змінної (з башизмами )

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

Якщо я просто хочу використане значення:

array=($(df -k /))

ви могли бачити змінну масиву :

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]="/")'

Тоді:

echo ${array[9]}
529020

Але я віддаю перевагу цьому:

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

Перший read fooпросто пропустить рядок заголовка, але лише в одній команді ви заселите 7 різних змінних:

declare -p avail filesystem foo mountpoint prct size using
declare -- avail="401488"
declare -- filesystem="/dev/dm-0"
declare -- foo="Filesystem     1K-blocks   Used Available Use% Mounted on"
declare -- mountpoint="/"
declare -- prct="57%"
declare -- size="999320"
declare -- using="529020"

Або навіть:

{ read foo ; read filesystem dsk[{6,2,9}] prct mountpoint ; } < <(df -k /)
declare -p mountpoint dsk
declare -- mountpoint="/"
declare -a dsk=([2]="529020" [6]="999320" [9]="401488")

... також буде працювати з асоціативними масивами :read foo disk[total] disk[used] ...

Зразок функції для заповнення деяких змінних:

#!/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

Примітка: declareрядок не потрібен, лише для читання.

Про sudo cmd | grep ... | cut ...

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

(Будь ласка, уникайте марного cat! Отже, це лише на одну виделку менше:

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

Усі труби ( |) мають на увазі вилки. Там, де потрібно запустити інший процес, отримати доступ до диска, викликів бібліотек тощо.

Таким чином, використовуючи sedдля зразка, обмежте підпроцес лише однією вилкою :

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

І з башизмами :

Але для багатьох дій, в основному на невеликих файлах, Bash міг би виконати роботу сам:

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

або

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

Далі про змінне розділення ...

Подивіться на мою відповідь на тему: Як розділити рядок на роздільнику в Bash?

Альтернатива: зменшення вил за допомогою фонових тривалих завдань

2-е редагування 2018-02-12:

Для того, щоб запобігти кілька вилок, як

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

або

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

Це добре працює, але працює багато вилок важко і повільно.

І команди люблять dateі bcможуть робити багато операцій, рядок за рядком !!

Подивитися:

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

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

Таким чином, ми могли б використовувати тривалий фоновий процес, щоб зробити багато завдань, без необхідності ініціювати нову виделку для кожного запиту.

Для цього нам просто потрібні дескриптори файлів і фіфоси :

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

(Звичайно, FD 5і 6повинні бути невикористані!) ... Звідти ви можете використовувати цей процес, використовуючи:

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

У функцію newConnector

Ви можете знайти мою newConnectorфункцію в GitHub.Com або на моєму власному сайті (Примітка на GitHub: на моєму сайті є два файли. Функція та демонстрація об'єднані в один файл, який можна отримати для використання або просто запустити для демонстрації.)

Зразок:

. 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"

Функція myBcдозволяє використовувати фонове завдання з простим синтаксисом та для дати:

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

Звідси, якщо ви хочете закінчити один із фонових процесів, вам просто потрібно закрити його 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

що не потрібно, тому що всі fd закриваються, коли основний процес закінчується.


Вкладений вище зразок - це те, що я шукав. Може бути і більш простий спосіб, але те, що я шукав, це спосіб дізнатися, чи є контейнер докера вже даним його ім'ям в змінній середовища. Тож для мене: чи EXISTING_CONTAINER=$(docker ps -a | grep "$(echo $CONTAINER_NAME)")була заява, яку я шукав.
Capricorn1

2
@ capricorn1 Це марне використанняecho ; хочеш простоgrep "$CONTAINER_NAME"
трійця


Я, мабуть, щось тут сумую: kubectl get ns | while read -r line; do echo $ line | греп Термін | cut -d '' -f1 ; doneдрукує для кожного $lineпорожнього рядка, а потім bash: xxxx: command not found. Однак я б очікував, що вона вийде простоxxx
папаніто

77

Як вони вже вказували вам, вам слід скористатися "backticks".

Також запропонована альтернатива $(command)працює, і її також простіше читати, але зауважте, що вона діє лише з Bash або KornShell (і оболонками, отриманими з них), тому, якщо ваші сценарії повинні бути справді портативними в різних системах Unix, вам слід віддати перевагу старі позначення backticks.


23
Вони відверто обережні. Попередні позови були позбавлені POSIX давно; більш сучасний синтаксис повинен бути доступний у більшості оболонок цього тисячоліття. (Є все ще застарілі середовища, що кашляють HP-UX кашель, які міцно застрягли на початку дев'яностих.)
триплея

25
Неправильно. $()повністю сумісний з POSIX sh, як це було стандартизовано понад два десятиліття тому.
Чарльз Даффі

3
Зауважте, що /bin/shна Solaris 10 все ще не розпізнається $(…)- і AFAIK це правда і для Solaris 11.
Джонатан Леффлер

2
@JonathanLeffler Насправді більше немає справи з Solaris 11, де /bin/shє ksh93.
jlliagre

2
@tripleee - відповідь на три роки пізніше :-), але я використовував $()в оболонці POSIX на HP-UX протягом останніх 10+ років.
Боб Джарвіс - Відновіть Моніку

54

Я знаю три способи зробити це:

  1. Функції підходять для таких завдань: **

    func (){
        ls -l
    }

    Викликайте це, сказавши func.

  2. Також може бути прийнято ще одне підходяще рішення:

    var="ls -l"
    eval $var
  3. Третя безпосередньо використовує змінні:

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

Ви можете отримати результат третього рішення хорошим способом:

echo "$var"

А також неприємно:

echo $var

1
Перші два, здається, не відповідають на питання, як це існує, а друге, як правило, вважається сумнівним.
tripleee

1
Як хтось, хто абсолютно новий в башті, чому це "$var"добре і $varпротивно?
Пітер


30

Просто щоб бути іншим:

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

22

Встановлюючи змінну, переконайтеся, що у вас немає пробілів до та / або після = знака . Буквально витратили годину, намагаючись розібратися в цьому, пробуючи всілякі рішення! Це не круто.

Правильно:

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

Не вдасться з помилкою "речі: не знайдено" чи подібними

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

2
Версія з пробілом означає щось інше : var=value somecommandпрацює somecommandіз varсвоїм середовищем, яке має значення value. Таким чином, var= somecommandекспортується varв середовище somecommandз порожнім (нульовим байтом) значенням.
Чарльз Даффі

Так, баш готча.
Пітер Мортенсен

14

Якщо ви хочете зробити це з багаторядковими / декількома командами / секціями, ви можете зробити це:

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

Або:

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

Приклад:

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

Вихід:

first
second
third

Використовуючи heredoc , ви можете легко спростити речі, розбивши довгий однорядковий код на багаторядковий. Ще один приклад:

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

2
Що з другим bashвсередині підстановки команд? Ви вже створюєте підзаголовок самою підстановою команди. Якщо ви хочете поставити кілька команд, просто розділіть їх на новий рядок або крапку з комою. output=$(echo first; echo second; ...)
трійчатка

Тоді подібне 'bash -c "bash -c \"bash -c ...\""'було б і "іншим"; але я не бачу сенсу в цьому.
трійка

@tripleee heredoc означає щось більше, ніж це. Ви можете зробити те ж саме з іншими командами, як ssh sudo -sвиконання команд mysql всередині тощо. (Замість bash)
Jahid

1
Я не відчуваю, що ми спілкуємося належним чином. Я оскаржуючи корисність над variable=$(bash -c 'echo "foo"; echo "bar"')більш variable=$(echo "foo"; echo "bar")- тут - документ тільки з посиланням на механізм і на самому ділі не додає нічого , крім іншого непотрібного ускладнення.
трійчанка

2
Коли я використовую heredoc з ssh, я точно вказую команду для виконання ssh -p $port $user@$domain /bin/bash <<EOF, щоб запобігти Pseudo-terminal will not be allocated because stdin is not a terminal.попередженню
Ф. Хаурі

9

Вам потрібно використовувати будь-який

$(command-here)

або

`command-here`

Приклад

#!/bin/bash

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

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

echo "$MOREF"


1
Я не знав, що ви можете гніздо, але це має ідеальний сенс, дуже дякую за інформацію!
Дієго Велес

6

Це ще один спосіб, який добре використовувати з деякими текстовими редакторами, які не в змозі правильно виділити кожен створений вами складний код:

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

Це не стосується питання ОП, яке стосується насправді заміни команд , а не заміни процесу .
codeforester

6

Ви можете використовувати зворотні посилання (також відомі як могили з акцентами) або $() .

Подібно до:

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

Обидва мають однаковий ефект. Але OUTPUT = $ (x + 2) є більш читабельним та останнім.


2
Парентез був здійснений для того, щоб дозволити гніздування.
Ф. Хаурі

5

Якщо команда, яку ви намагаєтеся виконати, не вдасться, вона запише висновок у потік помилок і потім буде надрукована на консоль.

Щоб цього уникнути, потрібно перенаправити потік помилок:

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

4

Деякі можуть вважати це корисним. Значення цілого числа в змінній підстановці, де фокусом є використання $(())подвійних дужок:

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

1
Схоже, це питання не має жодного значення для цього питання. Було б розумною відповіддю, якби хтось запитував, як помножити число в масиві на постійний коефіцієнт, хоча я не пригадую, щоб коли-небудь бачив, щоб хтось це запитував (і тоді for ((...))цикл здався б кращим відповідним для змінної циклу ). Крім того, не слід використовувати великі регістри для ваших приватних змінних.
трійка

Я не погоджуюся з частиною "актуальності". Питання чітко звучить: Як встановити змінну, рівну виводу з команди в Bash? І я додав цю відповідь як доповнення, тому що я прийшов сюди шукати рішення, яке допомогло мені з кодом, який я згодом опублікував. Що стосується великих регістрів, дякую за це.
Гас

1
Це можна написати, ARR=(3 2 4 1);for((N=3,M=3,COUNT=N-1;COUNT < ${#ARR[@]};ARR[COUNT]*=M,COUNT+=N)){ :;}але я погоджуюся з @tripleee: я не розумію, що це робити, там!
Ф. Хаурі

@ F.Hauri ... Баш стає все більше і більше схожим на перл, чим глибше ти заглибишся в нього!
roblogic

4

Ось ще два способи:

Будь ласка, майте на увазі, що простір дуже важливий у Bash. Отже, якщо ви хочете, щоб ваша команда виконувалася, використовуйте як є, не вводячи більше пробілів.

  1. Наступні правонаступник harshilдо Lі потім виводить його

    L=$"harshil"
    echo "$L"
  2. Далі призначається вихід команди trз L2. trексплуатується на іншій змінній, L1.

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

4
1. $"..."Мабуть, не робить те, що, на вашу думку, робить . 2. Про це вже сказано у відповіді Енді Лестера.
gniourf_gniourf

@gniourf_gniourf вірно: дивіться, що локалізація bash не працюватиме з мультилініями . Але під bash , ви можете використовувати echo ${L1,,}для заміни, або echo ${L1^^}для заміни.
Ф. Хаурі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.