scripts script programacion pasar parametros operaciones manejo funciones ejemplos cadenas aritmeticas string bash function return-value

string - script - Cómo devolver un valor de cadena desde una función Bash



scripts bash ejemplos (18)

Bash, desde la versión 4.3, febrero de 2014 (?), Tiene soporte explícito para variables de referencia o referencias de nombre (namerefs), más allá de "eval", con el mismo efecto beneficioso y efecto de indirección, y que puede ser más claro en sus scripts y también más difícil Para "olvidarse de ''eval'' y tener que corregir este error":

declare [-aAfFgilnrtux] [-p] [name[=value] ...] typeset [-aAfFgilnrtux] [-p] [name[=value] ...] Declare variables and/or give them attributes ... -n Give each name the nameref attribute, making it a name reference to another variable. That other variable is defined by the value of name. All references and assignments to name, except for⋅ changing the -n attribute itself, are performed on the variable referenced by name''s value. The -n attribute cannot be applied to array variables. ... When used in a function, declare and typeset make each name local, as with the local command, unless the -g option is supplied...

y también:

Los parámetros

A una variable se le puede asignar el atributo nameref mediante la opción -n a los comandos declarados o locales incorporados (consulte las descripciones de declarar y local a continuación) para crear un nameref, o una referencia a otra variable. Esto permite que las variables sean manipuladas indirectamente. Cuando la variable nameref es referenciada o asignada, la operación se realiza realmente en la variable especificada por el valor de la variable nameref. Una nameref se usa comúnmente dentro de las funciones de shell para referirse a una variable cuyo nombre se pasa como un argumento a la función. Por ejemplo, si un nombre de variable se pasa a una función de shell como su primer argumento, ejecutando

declare -n ref=$1

dentro de la función se crea una referencia de referencia de referencia cuyo valor es el nombre de la variable que se pasa como primer argumento. Las referencias y asignaciones a ref se tratan como referencias y asignaciones a la variable cuyo nombre se pasó como $ 1. Si la variable de control en un bucle for tiene el atributo nameref, la lista de palabras puede ser una lista de variables de shell, y se establecerá una referencia de nombre para cada palabra de la lista, a su vez, cuando se ejecute el bucle. Las variables de matriz no pueden recibir el atributo -n. Sin embargo, las variables nameref pueden hacer referencia a variables de matriz y variables de matriz con subíndices. Namerefs puede ser ⋅ desactivado usando la opción -n para el desarmado incorporado. De lo contrario, si unset se ejecuta con el nombre de una variable nameref como argumento, la variable a la que hace referencia variable la variable nameref no se establecerá.

Por ejemplo ( EDIT 2 : (gracias, Ron) espaciado con el nombre (prefijo) el nombre de la variable interna de la función, para minimizar los choques de variables externas, que finalmente deberían responder correctamente, el problema planteado en los comentarios de Karsten):

# $1 : string; your variable to contain the return value function return_a_string () { declare -n ret=$1 local MYLIB_return_a_string_message="The date is " MYLIB_return_a_string_message+=$(date) ret=$MYLIB_return_a_string_message }

y probando este ejemplo:

$ return_a_string result; echo $result The date is 20160817

Tenga en cuenta que el bash "declare" builtin, cuando se usa en una función, hace que la variable declarada sea "local" por defecto, y "-n" también se puede usar con "local".

Prefiero distinguir las variables "declarar importante" de las variables "aburridas locales", por lo que usar "declarar" y "local" de esta manera actúa como documentación.

EDITAR 1 - (Respuesta al comentario de Karsten a continuación) - No puedo agregar comentarios a continuación más, pero el comentario de Karsten me hizo pensar, así que hice la siguiente prueba que FUNCIONA BIEN, AFAICT - Karsten, si leen esto, proporcionen un conjunto exacto. de los pasos de prueba desde la línea de comandos, que muestra el problema que usted asume que existe, porque estos pasos siguientes funcionan bien:

$ return_a_string ret; echo $ret The date is 20170104

(Corrí esto justo ahora, después de pegar la función anterior en un término de bash, como puede ver, el resultado funciona bien).

Me gustaría devolver una cadena de una función Bash.

Escribiré el ejemplo en java para mostrar lo que me gustaría hacer:

public String getSomeString() { return "tadaa"; } String variable = getSomeString();

El siguiente ejemplo funciona en bash, pero ¿hay una mejor manera de hacerlo?

function getSomeString { echo "tadaa" } VARIABLE=$(getSomeString)


Como bstpierre anterior, uso y recomiendo el uso de nombrar explícitamente las variables de salida:

function some_func() # OUTVAR ARG1 { local _outvar=$1 local _result # Use some naming convention to avoid OUTVARs to clash ... some processing .... eval $_outvar=/$_result # Instead of just =$_result }

Tenga en cuenta el uso de citar el $. Esto evitará interpretar el contenido en $result como caracteres especiales de shell. Descubrí que este es un orden de magnitud más rápido que el result=$(some_func "arg1") idioma de capturar un eco. La diferencia de velocidad parece aún más notable si se usa bash en MSYS, donde la captura de la salida de programa de las llamadas a funciones es casi catastrófica.

Está bien enviar variables locales ya que los locales tienen un alcance dinámico en bash:

function another_func() # ARG { local result some_func result "$1" echo result is $result }


Como se mencionó anteriormente, la forma "correcta" de devolver una cadena desde una función es mediante la sustitución de comandos. En el caso de que la función también deba enviarse a la consola (como se menciona anteriormente en @Mani), cree un fd temporal al principio de la función y rediríjalo a la consola. Cierre el fd temporal antes de devolver su cadena.

#!/bin/bash # file: func_return_test.sh returnString() { exec 3>&1 >/dev/tty local s=$1 s=${s:="some default string"} echo "writing directly to console" exec 3>&- echo "$s" } my_string=$(returnString "$*") echo "my_string: [$my_string]"

ejecutar script sin params produce ...

# ./func_return_test.sh writing directly to console my_string: [some default string]

espero que esto ayude a la gente

-Andy


Dirigiéndose a la cabeza de Vicky Ronnen , considerando el siguiente código:

function use_global { eval "$1=''changed using a global var''" } function capture_output { echo "always changed" } function test_inside_a_func { local _myvar=''local starting value'' echo "3. $_myvar" use_global ''_myvar'' echo "4. $_myvar" _myvar=$( capture_output ) echo "5. $_myvar" } function only_difference { local _myvar=''local starting value'' echo "7. $_myvar" local use_global ''_myvar'' echo "8. $_myvar" local _myvar=$( capture_output ) echo "9. $_myvar" } declare myvar=''global starting value'' echo "0. $myvar" use_global ''myvar'' echo "1. $myvar" myvar=$( capture_output ) echo "2. $myvar" test_inside_a_func echo "6. $_myvar" # this was local inside the above function only_difference



daré

0. global starting value 1. changed using a global var 2. always changed 3. local starting value 4. changed using a global var 5. always changed 6. 7. local starting value 8. local starting value 9. always changed

Tal vez el escenario normal es usar la sintaxis utilizada en la función test_inside_a_func , por lo que puede usar ambos métodos en la mayoría de los casos, aunque capturar la salida es el método más seguro que siempre funciona en cualquier situación, imitando el valor de retorno de una función que Se puede encontrar en otros idiomas, como Vicky Ronnen señaló correctamente.


El problema clave de cualquier esquema de ''variable de salida con nombre'' en el que la persona que llama puede pasar el nombre de la variable (ya sea utilizando eval o declare -n ) es un alias involuntario, es decir, conflictos de nombres: desde el punto de vista de la encapsulación, es horrible no poder para agregar o cambiar el nombre de una variable local en una función sin revisar TODAS las personas que llaman a la función para asegurarse de que no desean pasar el mismo nombre que el parámetro de salida. (O en la otra dirección, no quiero tener que leer la fuente de la función que estoy llamando solo para asegurarme de que el parámetro de salida que pretendo usar no sea un local en esa función).

La única forma de Evi1M4chine es usar una única variable de salida dedicada como REPLY (como lo sugiere Evi1M4chine ) o una convención como la sugerida por Ron Burk .

Sin embargo, es posible que las funciones usen una variable de salida fija internamente y luego agreguen algo de azúcar en la parte superior para ocultar este hecho a la persona que llama , como he hecho con la función de call en el siguiente ejemplo. Considere esto como una prueba de concepto, pero los puntos clave son

  • La función siempre asigna el valor de retorno a REPLY , y también puede devolver un código de salida como de costumbre.
  • Desde la perspectiva de la persona que llama, el valor de retorno se puede asignar a cualquier variable (local o global), incluida la REPLY (consulte el ejemplo del wrapper ). El código de salida de la función se pasa, por lo que usarlos en, por ejemplo, if o while o construcciones similares funciona como se espera.
  • Sintácticamente, la llamada a la función sigue siendo una sola declaración simple.

La razón por la que esto funciona es porque la función de call sí misma no tiene locales y no utiliza otras variables además de REPLY , evitando cualquier posibilidad de conflicto de nombres. En el punto donde se asigna el nombre de la variable de salida definida por el llamante, estamos efectivamente en el alcance del llamante (técnicamente en el alcance idéntico de la función de call ), en lugar del alcance de la función a la que se llama.

#!/bin/bash function call() { # var=func [args ...] REPLY=; "${1#*=}" "${@:2}"; eval "${1%%=*}=/$REPLY; return $?" } function greet() { case "$1" in us) REPLY="hello";; nz) REPLY="kia ora";; *) return 123;; esac } function wrapper() { call REPLY=greet "$@" } function main() { local a b c d call a=greet us echo "a=''$a'' ($?)" call b=greet nz echo "b=''$b'' ($?)" call c=greet de echo "c=''$c'' ($?)" call d=wrapper us echo "d=''$d'' ($?)" } main

Salida:

a=''hello'' (0) b=''kia ora'' (0) c='''' (123) d=''hello'' (0)


En mis programas, por convención, esto es para lo que es la variable $REPLY preexistente, que la read utiliza para ese propósito exacto.

function getSomeString { REPLY="tadaa" } getSomeString echo $REPLY

Este echo es

tadaa

Pero para evitar conflictos, cualquier otra variable global servirá.

declare result function getSomeString { result="tadaa" } getSomeString echo $result

Si eso no es suficiente, recomiendo la solución de Markarian451 .


La forma en que lo tiene es la única forma de hacerlo sin romper el alcance. Bash no tiene un concepto de tipos de retorno, solo salir de códigos y descriptores de archivos (stdin / out / err, etc.)


La solución más directa y robusta es usar la sustitución de comandos, como escribieron otras personas:

assign() { local x x="Test" echo "$x" } x=$(assign) # This assigns string "Test" to x

El inconveniente es el rendimiento, ya que esto requiere un proceso separado.

La otra técnica sugerida en este tema, a saber, pasar el nombre de una variable para asignar como argumento, tiene efectos secundarios, y no lo recomendaría en su forma básica. El problema es que probablemente necesitará algunas variables en la función para calcular el valor de retorno, y puede suceder que el nombre de la variable que pretende almacenar el valor de retorno interfiera con uno de ellos:

assign() { local x x="Test" eval "$1=/$x" } assign y # This assigns string "Test" to y, as expected assign x # This will NOT assign anything to x in this scope # because the name "x" is declared as local inside the function

Puede, por supuesto, no declarar las variables internas de la función como locales, pero realmente debería hacerlo siempre que, de lo contrario, puede, por otro lado, sobrescribir accidentalmente una variable no relacionada del ámbito principal si hay una con el mismo nombre .

Una posible solución es una declaración explícita de la variable pasada como global:

assign() { local x eval declare -g $1 x="Test" eval "$1=/$x" }

Si se pasa el nombre "x" como argumento, la segunda fila del cuerpo de la función sobrescribirá la declaración local anterior. Pero los nombres en sí mismos podrían interferir, así que si pretende usar el valor previamente almacenado en la variable pasada antes de escribir el valor de retorno allí, tenga en cuenta que debe copiarlo en otra variable local desde el principio; De lo contrario el resultado será impredecible! Además, esto solo funcionará en la versión más reciente de BASH, es decir, 4.2. Un código más portátil podría utilizar construcciones condicionales explícitas con el mismo efecto:

assign() { if [[ $1 != x ]]; then local x fi x="Test" eval "$1=/$x" }

Tal vez la solución más elegante es reservar un nombre global para los valores de retorno de la función y usarlo de manera consistente en cada función que escriba.


Las opciones han sido todas enumeradas, creo. Elegir uno puede llegar a ser el mejor estilo para su aplicación particular, y en ese sentido, quiero ofrecer un estilo particular que he encontrado útil. En bash, las variables y las funciones no están en el mismo espacio de nombres. Por lo tanto, tratar la variable del mismo nombre como el valor de la función es una convención que encuentro que minimiza los choques de nombres y mejora la legibilidad, si la aplico con rigor. Un ejemplo de la vida real:

UnGetChar= function GetChar() { # assume failure GetChar= # if someone previously "ungot" a char if ! [ -z "$UnGetChar" ]; then GetChar="$UnGetChar" UnGetChar= return 0 # success # else, if not at EOF elif IFS= read -N1 GetChar ; then return 0 # success else return 1 # EOF fi } function UnGetChar(){ UnGetChar="$1" }

Y, un ejemplo del uso de tales funciones:

function GetToken() { # assume failure GetToken= # if at end of file if ! GetChar; then return 1 # EOF # if start of comment elif [[ "$GetChar" == "#" ]]; then while [[ "$GetChar" != $''/n'' ]]; do GetToken+="$GetChar" GetChar done UnGetChar "$GetChar" # if start of quoted string elif [ "$GetChar" == ''"'' ]; then # ... et cetera

Como puede ver, el estado de retorno está allí para que lo use cuando lo necesite, o lo ignore si no lo hace. La variable "devuelta" también se puede usar o ignorar, pero por supuesto solo después de invocar la función.

Por supuesto, esto es sólo una convención. Usted es libre de no establecer el valor asociado antes de regresar (de ahí mi convención de siempre anularlo al inicio de la función) o de pisotear su valor llamando a la función nuevamente (posiblemente de manera indirecta). Aún así, es una convención que me parece muy útil si me encuentro haciendo un uso intensivo de las funciones de bash.

A diferencia del sentimiento de que esto es un signo, por ejemplo, "mudarse a perl", mi filosofía es que las convenciones son siempre importantes para gestionar la complejidad de cualquier idioma.


No hay mejor manera que yo sepa. Bash solo conoce los códigos de estado (enteros) y las cadenas escritas en la salida estándar.


Para ilustrar mi comentario sobre la respuesta de Andy, con manipulación adicional del descriptor de archivos para evitar el uso de /dev/tty :

#!/bin/bash exec 3>&1 returnString() { exec 4>&1 >&3 local s=$1 s=${s:="some default string"} echo "writing to stdout" echo "writing to stderr" >&2 exec >&4- echo "$s" } my_string=$(returnString "$*") echo "my_string: [$my_string]"

Sin embargo, sigue siendo desagradable.


Patrón bash para devolver objetos de valor escalar y de matriz :

definición

url_parse() { # parse ''url'' into: ''url_host'', ''url_port'', ... local "$@" # inject caller ''url'' argument in local scope local url_host="..." url_path="..." # calculate ''url_*'' components declare -p ${!url_*} # return only ''url_*'' object fields to the caller }

invocación

main() { # invoke url parser and inject ''url_*'' results in local scope eval "$(url_parse url=http://host/path)" # parse ''url'' echo "host=$url_host path=$url_path" # use ''url_*'' components }


Podría hacer que la función tome una variable como primer argumento y modifique la variable con la cadena que desea devolver.

#!/bin/bash set -x function pass_back_a_string() { eval "$1=''foo bar rab oof''" } return_var='''' pass_back_a_string return_var echo $return_var

Impresiones "foo bar rab oof".

Edición : se agregaron las citas en el lugar apropiado para permitir que los espacios en blanco en la cadena aborden el comentario de @Luca Borrione.

Edición : Como demostración, vea el siguiente programa. Esta es una solución de propósito general: incluso le permite recibir una cadena en una variable local.

#!/bin/bash set -x function pass_back_a_string() { eval "$1=''foo bar rab oof''" } return_var='''' pass_back_a_string return_var echo $return_var function call_a_string_func() { local lvar='''' pass_back_a_string lvar echo "lvar=''$lvar'' locally" } call_a_string_func echo "lvar=''$lvar'' globally"

Esto imprime:

+ return_var= + pass_back_a_string return_var + eval ''return_var=''/'''foo bar rab oof''/''''' ++ return_var=''foo bar rab oof'' + echo foo bar rab oof foo bar rab oof + call_a_string_func + local lvar= + pass_back_a_string lvar + eval ''lvar=''/'''foo bar rab oof''/''''' ++ lvar=''foo bar rab oof'' + echo ''lvar=''/'''foo bar rab oof''/''' locally'' lvar=''foo bar rab oof'' locally + echo ''lvar=''/'''''/''' globally'' lvar='''' globally

Edición : demostrar que el valor de la variable original está disponible en la función, como fue criticado incorrectamente por @Xichen Li en un comentario.

#!/bin/bash set -x function pass_back_a_string() { eval "echo in pass_back_a_string, original $1 is /$$1" eval "$1=''foo bar rab oof''" } return_var=''original return_var'' pass_back_a_string return_var echo $return_var function call_a_string_func() { local lvar=''original lvar'' pass_back_a_string lvar echo "lvar=''$lvar'' locally" } call_a_string_func echo "lvar=''$lvar'' globally"

Esto da salida:

+ return_var=''original return_var'' + pass_back_a_string return_var + eval ''echo in pass_back_a_string, original return_var is $return_var'' ++ echo in pass_back_a_string, original return_var is original return_var in pass_back_a_string, original return_var is original return_var + eval ''return_var=''/'''foo bar rab oof''/''''' ++ return_var=''foo bar rab oof'' + echo foo bar rab oof foo bar rab oof + call_a_string_func + local ''lvar=original lvar'' + pass_back_a_string lvar + eval ''echo in pass_back_a_string, original lvar is $lvar'' ++ echo in pass_back_a_string, original lvar is original lvar in pass_back_a_string, original lvar is original lvar + eval ''lvar=''/'''foo bar rab oof''/''''' ++ lvar=''foo bar rab oof'' + echo ''lvar=''/'''foo bar rab oof''/''' locally'' lvar=''foo bar rab oof'' locally + echo ''lvar=''/'''''/''' globally'' lvar='''' globally


Podrías usar una variable global:

declare globalvar=''some string'' string () { eval "$1=''some other string''" } # ---------- end of function string ---------- string globalvar echo "''${globalvar}''"

Esto da

''some other string''


Puede hacer echo una cadena, pero atraparla canalizando ( | ) la función a otra cosa.

Puede hacerlo con expr , aunque ShellCheck informa que este uso está en desuso.


También podría capturar la salida de la función:

#!/bin/bash function getSomeString() { echo "tadaa!" } return_var=$(getSomeString) echo $return_var # Alternative syntax: return_var=`getSomeString` echo $return_var

Parece extraño, pero es mejor que usar variables globales IMHO. Pasar parámetros funciona como es habitual, solo colóquelos dentro de las llaves o backticks.


Todas las respuestas anteriores ignoran lo que se ha indicado en la página man de bash.

  • Todas las variables declaradas dentro de una función se compartirán con el entorno de llamada.
  • Todas las variables declaradas locales no serán compartidas.

Código de ejemplo

#!/bin/bash f() { echo function starts local WillNotExists="It still does!" DoesNotExists="It still does!" echo function ends } echo $DoesNotExists #Should print empty line echo $WillNotExists #Should print empty line f #Call the function echo $DoesNotExists #Should print It still does! echo $WillNotExists #Should print empty line

Y salida

$ sh -x ./x.sh + echo + echo + f + echo function starts function starts + local ''WillNotExists=It still does!'' + DoesNotExists=''It still does!'' + echo function ends function ends + echo It still ''does!'' It still does! + echo

También bajo pdksh y ksh este script hace lo mismo!


agt@agtsoft:~/temp$ cat ./fc #!/bin/sh fcall=''function fcall { local res p=$1; shift; fname $*; eval "$p=$res"; }; fcall'' function f1 { res=$[($1+$2)*2]; } function f2 { local a; eval ${fcall//fname/f1} a 2 3; echo f2:$a; } a=3; f2; echo after:a=$a, res=$res agt@agtsoft:~/temp$ ./fc f2:10 after:a=3, res=