web-dev-qa-db-fra.com

Comment définir une variable sur la sortie d'une commande dans Bash?

J'ai un script assez simple qui ressemble à ce qui suit:

#!/bin/bash

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

echo $MOREF

Lorsque j'exécute ce script à partir de la ligne de commande et lui passe les arguments, je ne reçois aucun résultat. Cependant, lorsque je lance les commandes contenues dans la variable $MOREF, je peux obtenir une sortie.

Comment peut-on prendre les résultats d'une commande qui doit être exécutée dans un script, l'enregistrer dans une variable, puis afficher cette variable à l'écran?

1416
John

En plus des backticks `command`, vous pouvez utiliser $(command) ou "$(command)" que je trouve plus facile à lire et à permettre l’imbrication.

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

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

La citation (") est importante pour préserver les valeurs multilignes.

2109
Andy Lester

La bonne façon est

$(Sudo run command)

Si vous voulez utiliser une apostrophe, vous avez besoin de ` et non de '. Ce personnage est appelé "backticks" (ou "accent grave").

Comme ça:

#!/bin/bash

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

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

echo "$MOREF"
254
Ilya Kogan

Comme ils vous l'ont déjà indiqué, vous devez utiliser des "backticks".

L'alternative proposée $(command) fonctionne également et est également plus facile à lire, mais notez qu'elle n'est valide qu'avec Bash ou KornShell (et les shells dérivés de ceux-ci), donc si vos scripts doivent être réellement portables sur divers Unix systèmes, vous devriez préférer la vieille notation backticks.

67
bitwelder

Quelques astuces bash que j'utilise pour définir les variables à partir de commandes

2nd Edit 2018-02-12: Ajout d’une autre manière, recherchez en bas de ceci tâches longues !

2018-01-25 Edit: ajouter un exemple de fonction (pour renseigner les variables relatives à l'utilisation du disque)

Première manière simple, ancienne et compatible

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

Principalement compatible, deuxième voie

La nidification pouvant devenir lourde, une parenthèse a été mise en place pour cela.

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

Échantillon imbriqué:

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

lecture de plusieurs variables (avec bashisms )

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

Si je veux juste Used valeur:

array=($(df -k /))

vous pouviez voir array variable:

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

Ensuite:

echo ${array[9]}
529020

Mais je préfère ceci:

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

La première read foo ne fera que ignorer la ligne d'en-tête (la variable $foo contiendra quelque chose comme Filesystem 1K-blocks Used Available Use% Mounted on)

Exemple de fonction pour renseigner certaines variables:

#!/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 la ligne n'est pas requise, juste pour la lisibilité.

À propos de Sudo cmd | grep ... | cut ...

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

(S'il vous plaît évitez inutile cat! Donc, c'est juste 1 fourchette de moins:

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

Tous les tuyaux (|) impliquent des forks. Si un autre processus doit être exécuté, accéder au disque, aux appels aux bibliothèques, etc.

Donc, utiliser sed comme exemple limitera le sous-processus à un seul fork :

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

Et avec bashisms :

Mais pour de nombreuses actions, principalement sur de petits fichiers, bash pourrait faire le travail lui-même:

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

ou

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

Aller plus loin sur fractionnement de variable ...

Regardez ma réponse à Comment diviser une chaîne sur un délimiteur dans Bash?

Alternative: réduction forks en utilisant tâches de longue durée en arrière-plan

2nd Edit 2018-02-12: Afin d'éviter que plusieurs fourchettes ne soient comme

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

ou

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

Cela fonctionne bien, mais l'utilisation de plusieurs fourches est lourde et lente.

et des commandes telles que date et bc peuvent effectuer de nombreuses opérations, ligne par ligne !!

Voir:

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

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

Nous pourrions donc utiliser le processus d'arrière-plan long = pour créer de nombreux travaux, sans avoir à lancer de nouveau fork pour chaque demande.

Nous avons juste besoin de descripteurs de fichier et fifos pour le faire correctement:

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

(bien sûr, FD 5 et 6 doivent être inutilisés!) ... À partir de là, vous pouvez utiliser ce processus en:

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

Dans une fonction newConnector

Vous pouvez trouver ma fonction newConnector sur GitHub.Com ou sur mon propre site (nota sur github, il y a deux fichiers, sur mon site, ma fonction et ma démo sont regroupés dans un fichier pouvant être créé ou utilisé pour une démonstration)

Échantillon:

. 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 fonction myBc vous permet d'utiliser la tâche en arrière-plan avec une syntaxe simple et pour la date:

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

A partir de là, si vous souhaitez mettre fin à un processus d'arrière-plan, il vous suffit de fermer son 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

ce qui n'est pas nécessaire, car tous les fd se ferment à la fin du processus principal.

62
F. Hauri

Je connais trois façons de faire:

1) Les fonctions conviennent à de telles tâches:

func (){
ls -l
}

Invoquez-le en disant func

2) Une autre solution appropriée pourrait également être eval:

var="ls -l"
eval $var

) Le troisième utilise directement les variables:

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

vous pouvez obtenir le résultat de la troisième solution dans le bon sens:

echo "$var"

et aussi de manière méchante:

echo $var
52
MLSC

Juste pour être différent:

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

Lorsque vous définissez une variable, assurez-vous que vous avez NO Spaces avant et/ou après le signe =. J'ai littéralement passé une heure à essayer de comprendre cela, à essayer toutes sortes de solutions! C'est pas cool.

Correct:

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

échouera avec l'erreur suivante: (objet introuvable ou similaire)

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

Si vous voulez le faire avec plusieurs lignes/plusieurs commandes/s, vous pouvez le faire:

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

Ou:

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

Exemple:

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

Sortie:

first
second
third

En utilisant heredoc , vous pouvez simplifier les choses très facilement en décomposant votre code longue ligne unique en plusieurs lignes. Un autre exemple:

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

Vous devez utiliser soit

$(command-here)

ou

`command-here`

exemple

#!/bin/bash

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

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

echo "$MOREF"
8
Diego Velez

C’est un autre moyen, qu’il est bon d’utiliser avec certains éditeurs de texte qui ne peuvent pas mettre en évidence correctement chaque code complexe que vous créez.

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

Vous pouvez utiliser des ticks arrière (aussi appelés accent grave) ou $(). Comme en-

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

Les deux ont le même effet. Mais OUTPUT = $ (x + 2) est plus lisible et le plus récent.

6
Pratik Patil

Certains peuvent trouver cela utile. Valeurs entières en substitution de variable, où l’astuce consiste à utiliser $(()) double crochets:

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

Voici deux autres moyens:

S'il vous plaît gardez à l'esprit que l'espace est très important dans bash . Donc, si vous voulez que votre commande s'exécute, utilisez-la telle quelle sans introduire plus d'espaces.

  1. suivant assigne harshil à L puis l’imprime

    L=$"harshil"
    echo "$L"
    
  2. ce qui suit affecte la sortie de la commande tr à L2. tr est utilisé avec une autre variable L1.

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

Si la commande que vous essayez d'exécuter échoue, la sortie est écrite dans le flux d'erreur et ensuite imprimée sur la console. Pour l'éviter, vous devez rediriger le flux d'erreur

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