it-swarm.it

Come imposto una variabile sull'output di un comando in Bash?

Ho uno script piuttosto semplice che è qualcosa di simile al seguente:

#!/bin/bash

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

echo $MOREF

Quando eseguo questo script dalla riga di comando e gli passiamo gli argomenti, non ricevo alcun output. Tuttavia, quando eseguo i comandi contenuti nella variabile $MOREF, sono in grado di ottenere output.

Come si possono prendere i risultati di un comando che deve essere eseguito all'interno di uno script, salvarlo in una variabile e quindi emettere quella variabile sullo schermo?

1317
John

Oltre ai backtick (`command`), puoi usare $(command), che trovo più facile da leggere, e consente l'annidamento.

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

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

Citando (") importa per preservare i valori multi-linea.

1989
Andy Lester

La strada giusta è

$(Sudo run command)

Se stai per utilizzare un apostrofo, hai bisogno di `, non '. Questo personaggio è chiamato "backtick" (o "accento grave").

Come questo:

#!/bin/bash

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

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

echo "$MOREF"
240
Ilya Kogan

Come ti hanno già indicato, dovresti usare 'backtick'.

L'alternativa proposta $(command) funziona pure, ed è anche più facile da leggere, ma si noti che è valida solo con Bash o KornShell (e le shell derivate da quelli), quindi se i tuoi script devono essere veramente portabili su vari sistemi Unix, dovresti preferisco la notazione dei vecchi apici.

64
bitwelder

Alcuni bash trucchi che uso per impostare le variabili dai comandi

2nd Edit 2018-02-12: Aggiunta di un modo diverso, cerca in fondo a questo per long-running tasks!

2018-01-25 Modifica: aggiungi funzione di esempio (per popolare i vars sull'utilizzo del disco)

Primo modo semplice e compatibile

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

Principalmente compatibile, seconda via

Poiché la nidificazione potrebbe diventare pesante, per questo è stata implementata una parentesi

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

Campione annidato:

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

leggere più di una variabile (con bashismi)

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

Se voglio solo il valore Usato:

array=($(df -k /))

potresti vedere la variabile array:

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

Poi:

echo ${array[9]}
529020

Ma preferisco questo:

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

1st read foo sarà solo skip riga di intestazione (variabile $foo conterrà qualcosa come Filesystem 1K-blocks Used Available Use% Mounted on)

Funzione di esempio per popolare alcune variabili:

#!/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: la riga declare non è richiesta, solo per la leggibilità.

Informazioni su Sudo cmd | grep ... | cut ...

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

(Si prega di evitare inutili cat! Quindi questo è solo 1 fork less:

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

Tutte le pipe (|) implicano fork. Dove deve essere eseguito un altro processo, l'accesso al disco, le chiamate alle librerie e così via.

Quindi, utilizzando sed per esempio, limiterà il sottoprocesso a solo un fork:

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

E con bashismi:

Ma per molte azioni, principalmente su file di piccole dimensioni, bash potrebbe fare il lavoro da solo:

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

o

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

Andando oltre a variable splitting ...

Dai un'occhiata alla mia risposta a Come faccio a dividere una stringa su un delimitatore in Bash?

Alternativa: riduzione di fork utilizzando attività con background di lunga durata

2nd Modifica 2018-02-12:Per evitare più fork come

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

o

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

Questo funziona bene, ma l'esecuzione di molte forcelle è pesante e lenta.

e comandi come date e bc potrebbero eseguire molte operazioni, riga per riga !!

Vedere:

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

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

Quindi potremmo usare long running processo in background per fare molti lavori, senza dover avviare un nuovo fork per ogni richiesta.

Abbiamo solo bisogno di descrittori di file e fifos per farlo correttamente:

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

(ovviamente, FD 5 e 6 devono essere inutilizzati!) ... Da lì, è possibile utilizzare questo processo tramite:

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

In una funzione newConnector

Puoi trovare la mia funzione newConnector su GitHub.Com o su il mio sito (Nota su github, ci sono due file, sul mio sito, la funzione e la demo sono raggruppati in 1 file che potrebbe essere fornito per utilizzare o semplicemente eseguire per demo)

Campione:

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

La funzione myBc consente di utilizzare l'attività in background con la sintassi semplice e per la data:

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

Da lì, se vuoi terminare uno dei processi in background, devi solo chiudere il suo 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

che non è necessario, poiché tutti i fd si chiudono al termine del processo principale.

59
F. Hauri

Conosco tre modi per fare:

1) Le funzioni sono adatte per tali compiti:

func (){
ls -l
}

Richiamalo dicendo func

2) Anche un'altra soluzione adatta potrebbe essere valutata:

var="ls -l"
eval $var

3) Il terzo utilizza direttamente le variabili:

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

puoi ottenere l'output della terza soluzione in modo corretto:

echo "$var"

e anche in modo sgradevole:

echo $var
52
MLSC

Solo per essere diversi:

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

Se vuoi farlo con comandi/multiline/multiple, puoi farlo:

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

O:

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

Esempio:

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

Produzione:

first
second
third

Usando heredoc puoi semplificare le cose abbastanza facilmente suddividendo il tuo lungo codice a linea singola in uno multilinea. Un altro esempio:

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

Quando si imposta una variabile, assicurarsi di avere NO spazi prima e/o dopo il segno = . Letteralmente ho passato un'ora a cercare di capirlo, provando tutti i tipi di soluzioni! Questo non è bello.

Correggere:

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

Will Fail con errore: (materiale: non trovato o simile)

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

Devi usare entrambi

$(command-here)

o

`command-here`

esempio

#!/bin/bash

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

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

echo "$MOREF"
8
Diego Velez

Questo è un altro modo, buono da usare con alcuni editor di testo che non sono in grado di evidenziare correttamente tutti i codici complicati creati.

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

Puoi usare back-ticks (conosciuti anche come accenti) o $(). Come as-

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

Entrambi hanno lo stesso effetto. Ma OUTPUT = $ (x + 2) è più leggibile e l'ultimo.

6
Pratik Patil

Alcuni potrebbero trovare questo utile. Valori interi in sostituzione variabile, in cui il trucco utilizza $(()) doppie parentesi:

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

Ecco altri due modi:

Tieni presente che lo spazio è molto importante in bash . Quindi, se vuoi eseguire il comando, usa come è senza introdurre altri spazi.

  1. in seguito assegna harshil a L e poi lo stampa

    L=$"harshil"
    echo "$L"
    
  2. seguente assegna l'output del comando tr a L2. tr viene utilizzato su un'altra variabile L1.

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