it-swarm-es.com

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

Tengo un script bastante simple que es algo 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 comandos y le paso los argumentos, no obtengo ningún resultado. Sin embargo, cuando ejecuto los comandos contenidos en la variable $MOREF, 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 generar esa variable en la pantalla?

1317
John

Además de backticks (`command`), puede usar $(command), que me parece más fácil de leer y me permite anidar.

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

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

La cotización (") sí importa para preservar los valores de varias líneas.

1989
Andy Lester

El camino correcto es

$(Sudo run command)

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

Me gusta esto:

#!/bin/bash

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

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

echo "$MOREF"
240
Ilya Kogan

Como ya te han indicado, debes usar 'backticks'.

La alternativa $(command) propuesta también funciona, y también es más fácil de leer, pero tenga en cuenta que solo es válida 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 Prefiero la antigua notación de backticks.

64
bitwelder

Algunos bash trucos que utilizo para establecer variables a partir de comandos

Segunda edición 2018-02-12: Agregando una forma diferente, busque en la parte inferior de esta para {tareas de larga ejecución!

2018-01-25 Edición: agregar una función de muestra (para rellenar las variables sobre el uso del disco)

Primera forma antigua y compatible simple.

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

Mayormente compatible, segunda vía.

Como la anidación podría volverse pesada, se implementó un paréntesis para esto.

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

Muestra anidada:

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

leyendo 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 Usado valor:

array=($(df -k /))

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

Entonces:

echo ${array[9]}
529020

Pero prefiero esto:

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

Primero read foo solo saltar línea de encabezado (la variable $foo contendrá algo como Filesystem 1K-blocks Used Available Use% Mounted on)

Función de ejemplo para poblar 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: no se requiere declare line, solo para facilitar la lectura.

Acerca de Sudo cmd | grep ... | cut ...

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

(Por favor, evite cat inútil! Así que esto es solo 1 horquilla menos:

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

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

Entonces, usar sed para la muestra, limitará el subproceso a solo un fork:

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

o

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 de variables...

Eche un vistazo a mi respuesta a ¿Cómo se divide una cadena en un delimitador en Bash?

Alternativa: reducir forks usando tareas de larga ejecución en segundo plano

2nd Edit 2018-02-12:para evitar múltiples bifurcaciones como

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)

Esto funciona bien, pero correr muchos tenedores es pesado y lento.

y comandos como date y bc podrían hacer muchas operaciones, línea por línea !!

Ver:

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

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

Por lo tanto, podríamos usar el proceso de fondo ejecución larga para realizar muchos trabajos, sin tener que iniciar un nuevo fork para cada solicitud.

Solo necesitamos algunos descriptores de archivo y fifos para hacer esto correctamente:

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

(por supuesto, FD 5 y 6 no se deben usar) ... Desde allí, puede usar este proceso de la siguiente manera:

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

En una función newConnector

Puede encontrar mi función newConnector en GitHub.Com o en mi propio sitio (Nota en github, hay dos archivos, en mi sitio, la función y la demostración están agrupados en 1 archivo del cual se puede obtener utilizar o simplemente ejecutar para la demostración

Muestra:

. 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 usar la tarea en segundo plano con una sintaxis simple, y 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

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

59
F. Hauri

Conozco tres formas de hacer:

1) Las funciones son adecuadas para tales tareas:

func (){
ls -l
}

Invócalo 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`

usted puede obtener la salida de la tercera solución de buena manera:

echo "$var"

y también de manera desagradable:

echo $var
52
MLSC

Solo para ser diferente:

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

Si desea hacerlo con multiline/multiple command/s, 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

Usando heredoc puede simplificar las cosas bastante fácilmente dividiendo su código de una sola línea larga en multilínea. Otro ejemplo:

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

Cuando establezca una variable, asegúrese de tener SIN espacios antes y/o después del signo = . ¡Literalmente pasé una hora tratando de resolver esto, probando todo tipo de soluciones! Esto no es guay.

Correcto:

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

Fallará con error: (material: no encontrado o similar)

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

Necesitas usar cualquiera

$(command-here)

o

`command-here`

ejemplo

#!/bin/bash

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

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

echo "$MOREF"
8
Diego Velez

Esta es otra forma, buena para usar con algunos editores de texto que no pueden resaltar correctamente cada código intrincado que creas.

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

Puedes usar garrapatas (también conocidas como tumbas de acento) o $(). Como-

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

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

6
Pratik Patil

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

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

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, úselo tal cual sin introducir más espacios.

  1. a continuación se asigna Harshil a L y luego se imprime.

    L=$"harshil"
    echo "$L"
    
  2. a continuación se asigna la salida del comando tr a L2. tr está siendo operado en otra variable L1.

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