¿Cómo configuro una variable para la salida de un comando en Bash?

Tengo un script bastante simple que es algo así como lo siguiente:

#!/bin/bash

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

echo $MOREF

Cuando ejecuto este script desde la línea de comando y le paso los argumentos, no obtengo ningún resultado. Sin embargo, cuando ejecuto los comandos contenidos en el $MOREF variable, puedo obtener salida.

¿Cómo se pueden tomar los resultados de un comando que debe ejecutarse dentro de un script, guardarlo en una variable y luego mostrar esa variable en la pantalla?

preguntado el 10 de enero de 11 a las 17:01

Aparte, las variables en mayúsculas son definido por POSIX para nombres de variables con significado para el sistema operativo o el propio shell, mientras que los nombres con al menos un carácter en minúscula están reservados para el uso de la aplicación. Por lo tanto, considere usar nombres en minúsculas para sus propias variables de shell para evitar conflictos no deseados (teniendo en cuenta que la configuración de una variable de shell sobrescribirá cualquier variable de entorno con el mismo nombre). -

Como acotación al margen, la captura de salida en una variable solo para que luego pueda echo la variable es un uso inútil de echo, y un uso inútil de variables. -

Además, el almacenamiento de la salida en variables a menudo es innecesario. Para cadenas pequeñas y cortas, necesitará hacer referencia varias veces en su programa, esto está completamente bien y es exactamente el camino a seguir; pero para procesar cantidades de datos no triviales, desea remodelar su proceso en una canalización o usar un archivo temporal. -

Variación: "Yo se como usar variable=$(command) pero creo "$string" es un valido command"; stackoverflow.com/questions/37194795/… -

14 Respuestas

Además de las comillas invertidas `command`, sustitución de comando se puede hacer con $(command) or "$(command)", que encuentro más fácil de leer y permite anidar.

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

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

Citando (") importa preservar valores de variables de varias líneas; es opcional en el lado derecho de una tarea, ya que no se realiza la división de palabras, asi que OUTPUT=$(ls -1) funcionaría bien

contestado el 08 de mayo de 20 a las 22:05

¿Podemos proporcionar algún separador para salida multilínea? - Ario

El espacio en blanco (o la falta de espacio en blanco) es importante: Ali

@ timhc22, las llaves son irrelevantes; son solo las comillas las que son importantes con respecto a: si los resultados de expansión se dividen en cadenas y se expanden globalmente antes de pasar al echo mando. - Carlos Duffy

¡Ah gracias! Entonces, ¿hay algún beneficio para las llaves? - timhc22

Se pueden usar llaves cuando la variable va seguida inmediatamente por más caracteres que podrían interpretarse como parte del nombre de la variable. p.ej ${OUTPUT}foo. También son necesarios cuando se realizan operaciones de cadena en línea en la variable, como ${OUTPUT/foo/bar} - rico remer

$(sudo run command)

Si va a utilizar un apóstrofe, necesita `no, '. Este carácter se llama "comillas inversas" (o "acento grave"):

#!/bin/bash

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

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

echo "$MOREF"

Respondido 07 Feb 21, 21:02

La sintaxis de la comilla invertida es obsoleta, y realmente necesita poner comillas dobles alrededor de la interpolación de variables en el echo. - triples

Agregaría que debe tener cuidado con los espacios alrededor de '=' en la asignación anterior. Tú no debe tener espacios allí, de lo contrario obtendrá una asignación incorrecta - zbstof

El comentario de tripleeee es correcto. En cygwin (mayo de 2016), `` no funciona mientras $() obras. No se pudo arreglar hasta que vi esta página. - Toddwz

Elaboración como un ejemplo en Actualización (2018) sería apreciado. - Eduard

Algunos Asestar un golpe trucos que utilizo para establecer variables a partir de comandos

Lo siento, hay una respuesta larga, pero como es un , donde el objetivo principal es ejecutar otros comandos y reaccionar a resut codigo y/o salida, (los comandos a menudo se canalizan filtrar, etc ...).

Almacenar la salida del comando en variables es algo básico y fundamental.

Por tanto, dependiendo de

  • compatibilidad)
  • tipo de salida (filtro (s))
  • número de variable para establecer (dividir o interpretar)
  • tiempo de ejecución (seguimiento)
  • captura de errores
  • repetibilidad de la solicitud (consulte el proceso en segundo plano de larga ejecución, más adelante)
  • interactividad (teniendo en cuenta la entrada del usuario mientras lee de otro descriptor de archivo de entrada)
  • me pierdo algo?

Primera forma sencilla, antigua y compatible

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

Principalmente compatible, segunda vía

Como el anidamiento podría volverse pesado, se implementó paréntesis para este

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

Muestra anidada:

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

Características

Leer más de una variable (con Bashismos)

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

Si solo quiero un usado valor:

array=($(df -k /))

podrías ver un matriz 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]="/")'

Entonces:

echo ${array[9]}
529020

Pero a menudo uso esto:

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

El Primer read foo será sólo omitir línea de encabezado, pero en solo uno comando, lo harás poblar 7 diferentes variables:

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"

Or

{ read -a head;varnames=(${head[@]//[K1% -]});varnames=(${head[@]//[K1% -]});
  read ${varnames[@],,} ; } < <(LANG=C df -k /)

Entonces:

declare -p varnames ${varnames[@],,} 
declare -a varnames=([0]="Filesystem" [1]="blocks" [2]="Used" [3]="Available" [4]="Use" [5]="Mounted" [6]="on")
declare -- filesystem="/dev/dm-0"
declare -- blocks="999320"
declare -- used="529020"
declare -- available="401488"
declare -- use="57%"
declare -- mounted="/"
declare -- on=""

O incluso:

{ 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")

(Nota Used y Blocks se conmuta allí: read ... dsk[6] dsk[2] dsk[9] ...)

... trabajará con matrices asociativas también: read foo disk[total] disk[used] ...

Dedicado fd usar Fifo sin nombre:

Hay una forma elegante:

users=()
while IFS=: read -u $list user pass uid gid name home bin ;do
    ((uid>=500)) &&
        printf -v users[uid] "%11d %7d %-20s %s\n" $uid $gid $user $home
done {list}</etc/passwd

Usando de esta manera (... read -u $list; ... {list}<inputfile) licencia STDIN gratis para otros fines, como la interacción del usuario.

Entonces

echo -n "${users[@]}"
       1000    1000 user         /home/user
...
      65534   65534 nobody       /nonexistent

y

echo ${!users[@]}
1000 ... 65534

echo -n "${users[1000]}"
      1000    1000 user       /home/user

Esto podría usarse con archivos estáticos o incluso /dev/tcp/xx.xx.xx.xx/yyy con x para dirección IP o nombre de host y y para el número de puerto:

{
    read -u $list -a head          # read header in array `head`
    varnames=(${head[@]//[K1% -]}) # drop illegal chars for variable names
    while read -u $list ${varnames[@],,} ;do
        ((pct=available*100/(available+used),pct<10)) &&
            printf "WARN: FS: %-20s on %-14s %3d <10 (Total: %11u, Use: %7s)\n" \
                "${filesystem#*/mapper/}" "$mounted" $pct $blocks "$use"
     done
 } {list}< <(LANG=C df -k)

Y por supuesto con documentos en línea:

while IFS=\; read -u $list -a myvar ;do
    echo ${myvar[2]}
done {list}<<"eof"
foo;bar;baz
alice;bob;charlie
$cherry;$strawberry;$memberberries
eof

Función de muestra para completar algunas 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 no se requiere línea, solo para facilitar la lectura.

Acerca sudo cmd | grep ... | cut ...

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

(Evite inútiles cat! Así que esto es solo una bifurcación menos:

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

Todas las tuberías (|) implica bifurcaciones. Donde se debe ejecutar otro proceso, acceder al disco, llamadas a bibliotecas, etc.

Entonces usando sed para la muestra, limitará el subproceso a solo uno tenedor:

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

Y con Bashismos:

Pero para muchas acciones, principalmente en archivos pequeños, Bash podría hacer el trabajo por sí mismo:

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

or

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

Yendo más lejos sobre división variable...

Echa un vistazo a mi respuesta a ¿Cómo divido una cadena en un delimitador en Bash?

Alternativa: reducir tenedores mediante el uso tareas de larga duración en segundo plano

Para evitar múltiples bifurcaciones como

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

or

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

Esto funciona bien, pero ejecutar muchas bifurcaciones es pesado y lento.

Y comandos como date y bc podría hacer muchas operaciones, linea por linea!!

Ver:

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

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

Entonces podríamos usar un de larga duración proceso en segundo plano para realizar muchos trabajos, sin tener que iniciar un nuevo tenedor para cada solicitud.

Debajo , hay una función incorporada: coproc:

coproc bc -l
echo 4*3 >&${COPROC[1]}
read -u $COPROC answer
echo $answer
12

echo >&${COPROC[1]} 'pi=4*a(1)'
ray=42.0
printf >&${COPROC[1]} '2*pi*%s\n' $ray
read -u $COPROC answer
echo $answer
263.89378290154263202896

printf >&${COPROC[1]} 'pi*%s^2\n' $ray
read -u $COPROC answer
echo $answer
5541.76944093239527260816

As bc está listo, funcionando en segundo plano y las E / S también están listas, no hay demoras, no hay nada que cargar, abrir, cerrar, antes o después de la operación. ¡Solo la operación él mismo! Esto se vuelve mucho más rápido que tener que bifurcar bc para cada operación!

Efecto de borde: mientras bc seguir funcionando, mantendrán todos los registros, por lo que algunas variables o funciones podrían definirse en inicialización paso, como primero escribir a ${COPROC[1]}, justo después de comenzar la tarea (a través de coproc).

En una función newConnector

Puedes encontrar mi newConnector funcionar en GitHub.Com o en mi propio sitio (Nota sobre GitHub: hay dos archivos en mi sitio. La función y la demostración están empaquetadas en un archivo uniq que podría obtenerse para su uso o simplemente ejecutarse para la demostración).

Muestra:

source 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 función myBc le permite utilizar la tarea en segundo plano con una sintaxis simple.

Luego, para la fecha:

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 ahí, si desea finalizar uno de los procesos en segundo plano, solo tiene que cerrar su 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

lo cual no es necesario, porque todos los fd se cierran cuando finaliza el proceso principal.

respondido 31 mar '21, 11:03

La muestra anidada de arriba es lo que estaba buscando. Puede haber una forma más sencilla, pero lo que estaba buscando era la forma de averiguar si ya existe un contenedor de ventana acoplable dado su nombre en una variable de entorno. Entonces para mi: EXISTING_CONTAINER=$(docker ps -a | grep "$(echo $CONTAINER_NAME)") fue la declaración que estaba buscando. - Capricornio1

@ capricorn1 Eso es un uso inútil de echo; quieres simplemente grep "$CONTAINER_NAME" - triples

Probablemente me pierda algo aquí: kubectl get ns | while read -r line; do echo $ línea | término grep | cortar -d '' -f1; done imprime para cada $line una línea vacía y luego bash: xxxx: command not found. Sin embargo, esperaría que se imprima solo xxx - papanito

En lugar de todas las notas de "Ediciones" y tachaduras (para eso es el historial de revisiones), sería mejor tenerlo como si esta respuesta se hubiera escrito hoy. Si hay algunas lecciones que aprender, podría documentarse en una sección, p. Ej. "Cosas que no debes hacer". - Pedro Mortensen

Como ya te han indicado, debes usar 'comillas invertidas'.

La alternativa propuesta $(command) también funciona y es más fácil de leer, pero tenga en cuenta que solo es válido con Bash o KornShell (y shells derivados de ellos), por lo que si sus scripts tienen que ser realmente portátiles en varios sistemas Unix, debería preferir las antiguas comillas invertidas notación.

Respondido 11 Abr '19, 13:04

Son abiertamente cautelosos. Las comillas inversas han sido obsoletas por POSIX hace mucho tiempo; la sintaxis más moderna debería estar disponible en la mayoría de los shells de este milenio. (Todavía hay entornos heredados tosHP-UXtos que están atascados firmemente a principios de los noventa.) - triples

Incorrecto. $() es totalmente compatible con POSIX sh, estandarizado hace más de dos décadas. - Carlos Duffy

Tenga en cuenta que /bin/sh en Solaris 10 todavía no reconoce $(…) - Y AFAIK, eso también es cierto en Solaris 11. - jonathan leffler

@JonathanLeffler En realidad, ya no es el caso con Solaris 11 donde /bin/sh is ksh93. - jliagre

@tripleee - respuesta tres años tarde :-) pero he usado $() en el shell POSIX en HP-UX durante los últimos 10 años. - Bob Jarvis - Reincorporar a Monica

Conozco tres formas de hacerlo:

  1. Las funciones son adecuadas para tales tareas: **

    func (){
        ls -l
    }
    

    Invocarlo diciendo func.

  2. También otra solución adecuada podría ser eval:

    var="ls -l"
    eval $var
    
  3. El tercero está usando variables directamente:

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

Puede obtener el resultado de la tercera solución de una buena manera:

echo "$var"

Y también de una manera desagradable:

echo $var

respondido 18 nov., 19:14

Los dos primeros no parecen responder a la pregunta tal como está actualmente, y el segundo se considera comúnmente dudoso. - triples

Como alguien completamente nuevo en bash, ¿por qué "$var" bueno y $var ¿desagradable? - Peter

Solo para ser diferente:

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

Respondido el 10 de enero de 11 a las 21:01

Al configurar una variable, asegúrese de tener No hay espacios antes y / o después de la = firmar. ¡Literalmente pasé una hora tratando de resolver esto, probando todo tipo de soluciones! Esto es no fresco.

Correcto:

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

Fallará con el error "cosas: no encontradas" o similar

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

respondido 10 nov., 20:00

La versión con el espacio significa algo diferente: var=value somecommand corre somecommand con var en su entorno teniendo el valor value. Así, var= somecommand está exportando var en el entorno de somecommand con un valor vacío (cero bytes). - Carlos Duffy

Sí, un Bash te atrapó. - Pedro Mortensen

Si desea hacerlo con varios comandos / líneas múltiples, puede hacer esto:

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

o:

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

Ejemplo:

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

Salida:

first
second
third

Usar heredoc, puede simplificar las cosas con bastante facilidad dividiendo su código de una sola línea en uno de varias líneas. Otro ejemplo:

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

respondido 18 nov., 19:14

¿Qué pasa con el segundo bash dentro de la sustitución de comandos? Ya está creando una subcapa mediante la sustitución del comando en sí. Si desea colocar varios comandos, simplemente sepárelos con una nueva línea o un punto y coma. output=$(echo first; echo second; ...) - triples

Entonces de manera similar 'bash -c "bash -c \"bash -c ...\""' también sería "diferente"; pero no veo el sentido de eso. - triples

@tripleee heredoc significa algo más que eso. Puedes hacer lo mismo con algunos otros comandos como ssh sudo -s ejecutar comandos mysql dentro, etc. (en lugar de bash) - Yahid

No siento que nos estemos comunicando correctamente. Estoy desafiando la utilidad sobre variable=$(bash -c 'echo "foo"; echo "bar"') encima variable=$(echo "foo"; echo "bar") - El documento aquí es solo un mecanismo de cotización y en realidad no agrega nada excepto otra complicación inútil. - triples

Cuando uso heredoc con ssh, preciso el comando para ejecutar ssh -p $port $user@$domain /bin/bash <<EOF a fin de evitar Pseudo-terminal will not be allocated because stdin is not a terminal. advertencia - F.Hauri

Necesitas usar

$(command-here)

or

`command-here`

Ejemplo

#!/bin/bash

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

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

echo "$MOREF"

respondido 18 nov., 19:14

No sabía que podías anidar, pero tiene mucho sentido, ¡muchas gracias por la información! - Diego Vélez

Esta es otra forma y es buena para usar con algunos editores de texto que no pueden resaltar correctamente cada código intrincado que crea:

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

respondido 18 nov., 19:14

Esto no se ocupa de la pregunta de OP, que en realidad se trata de sustitución de comandono, sustitución de procesos. - códigoforester

Puede usar comillas invertidas (también conocidas como tumbas de acento) o $().

Me gusta:

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

Ambos tienen el mismo efecto. Pero OUTPUT = $ (x + 2) es más legible y el último.

respondido 18 nov., 19:14

Se implementó un paréntesis para permitir la anidación. - F.Hauri

x+2 no es un comando válido, en la mayoría de los lugares. En la medida en que esto no induzca a error a los principiantes a pensar que así es como se hace la aritmética, esto duplica las respuestas existentes. - triples

Si el comando que está intentando ejecutar falla, escribirá la salida en el flujo de error y luego se imprimirá en la consola.

Para evitarlo, debe redirigir el flujo de errores:

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

Respondido el 05 de diciembre de 19 a las 03:12

Aquí hay dos formas más:

Tenga en cuenta que el espacio es muy importante en Bash. Por lo tanto, si desea que su comando se ejecute, utilícelo como está sin introducir más espacios.

  1. La siguiente asigna harshil a L y luego lo imprime

    L=$"harshil"
    echo "$L"
    
  2. Lo siguiente asigna la salida del comando tr a L2. tr está siendo operado en otra variable, L1.

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

respondido 18 nov., 19:14

1. $"..." probablemente no hace lo que crees que hace. 2. Esto ya se da en la respuesta de Andy Lester. - gniourf_gniourf

@gniourf_gniourf tiene razón: ver la localización de bash no funcionará con líneas múltiples. Pero bajo golpear, podrías usar echo ${L1,,} bajar, o echo ${L1^^} para upcase. - F.Hauri

Algunos pueden encontrar esto útil. Valores enteros en sustitución de variables, donde el truco es usar $(()) corchetes dobles:

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

respondido 22 nov., 15:11

Esto no parece tener ninguna relevancia para esta pregunta. Sería una respuesta razonable si alguien preguntara cómo multiplicar un número en una matriz por un factor constante, aunque no recuerdo haber visto a nadie preguntando eso (y luego un for ((...)) loop parecería una mejor coincidencia para la variable loop). Además, no debe usar mayúsculas para sus variables privadas. - triples

No estoy de acuerdo con la parte de "relevancia". La pregunta dice claramente: ¿Cómo establecer una variable igual a la salida de un comando en Bash? Y agregué esta respuesta como complemento porque llegué aquí buscando una solución que me ayudó con el código que publiqué más tarde. Respecto a las vars mayúsculas, gracias por eso. - Gus

Esto podría estar escrito ARR=(3 2 4 1);for((N=3,M=3,COUNT=N-1;COUNT < ${#ARR[@]};ARR[COUNT]*=M,COUNT+=N)){ :;} pero estoy de acuerdo con @tripleee: ¡No entiendo qué hacer esto, ahí! - F.Hauri

@ F.Hauri ... ¡bash se parece cada vez más a perl cuanto más profundizas en él! - robologico

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.