Bash, referring to array by value? - linux

Is there some way to access a variable by referring to it by a value?
BAR=("hello", "world")
function foo() {
DO SOME MAGIC WITH $1
// Output the value of the array $BAR
}
foo "BAR"

Perhaps what you're looking for is indirect expansion. From man bash:
If the first character of parameter is an exclamation point (!), a level of variable indirection is introduced. Bash uses the
value of the variable formed from the rest of parameter as the name of the variable; this variable is then expanded and that value
is used in the rest of the substitution, rather than the value of parameter itself. This is known as indirect expansion. The
exceptions to this are the expansions of ${!prefix*} and ${!name[#]} described below. The exclamation point must immediately fol‐
low the left brace in order to introduce indirection.
Related docs: Shell parameter expansion (Bash Manual) and Evaluating indirect/reference variables (BashFAQ).
Here's an example.
$ MYVAR="hello world"
$ VARNAME="MYVAR"
$ echo ${!VARNAME}
hello world
Note that indirect expansion for arrays is slightly cumbersome (because ${!name[#]} means something else. See linked docs above):
$ BAR=("hello" "world")
$ v="BAR[#]"
$ echo ${!v}
hello world
$ v="BAR[0]"
$ echo ${!v}
hello
$ v="BAR[1]"
$ echo ${!v}
world
To put this in context of your question:
BAR=("hello" "world")
function foo() {
ARR="${1}[#]"
echo ${!ARR}
}
foo "BAR" # prints out "hello world"
Caveats:
Indirect expansion of the array syntax will not work in older versions of bash (pre v3). See BashFAQ article.
It appears you cannot use it to retrieve the array size. ARR="#${1}[#]" will not work. You can however work around this issue by making a copy of the array if it is not prohibitively large. For example:
function foo() {
ORI_ARRNAME="${1}[#]"
local -a ARR=(${!ORI_ARRNAME}) # make a local copy of the array
# you can now use $ARR as the array
echo ${#ARR[#]} # get size
echo ${ARR[1]} # print 2nd element
}

BAR=("hello", "world")
function foo() {
eval echo "\${$1[#]}"
}
foo "BAR"

You can put your arrays into a dictionary matched with their names. Then you can look up this dictionary to find your array and display its contents.

Related

Assigning one variable to another in Bash?

I have a doubt. When i declare a value and assign to some variable, I don't know how to reassign the same value to another variable. See the code snippet below.
#/bin/sh
#declare ARG1 to a
a=ARG1
#declaring $a to ARG2
ARG2=$`$a`
echo "ARG 2 = $ARG2"
It should display my output as
ARG 2 = ARG1
...but instead the actual output is:
line 5: ARG1: command not found
ARG 2 = $
To assign the value associated with the variable dest to the variable source, you need simply run dest=$source.
For example, to assign the value associated with the variable arg2 to the variable a:
a=ARG1
arg2=$a
echo "ARG 2 = $arg2"
The use of lower-case variable names for local shell variables is by convention, not necessity -- but this has the advantage of avoiding conflicts with environment variables and builtins, both of which use all-uppercase names by convention.
You may also want to alias rather than copy the variable. For example, if you need mutation. Or if you want to run a function multiple times on different variables. Here's how it works
Example:
C=cat
declare -n VAR=C
VAR+=" says Hi"
echo "$C" # prints "cat says Hi"
Example with arrays/dictionaries:
A=(a a a)
declare -n VAR=A # "-n" stands for "name", e.g. a new name for the same variable
VAR+=(b)
echo "${A[#]}" # prints "a a a b"
That is, VAR becomes effectively the same as the original variable. Instead of copying, you're adding an alias. Here's an example with functions:
function myFunc() {
local -n VAR="$1"
VAR="Hello from $2"
echo "I've set variable '$1' to value '$VAR'"
}
myFunc Inbox Bob # I've set variable 'Inbox' to value 'Hello from Bob'
myFunc Luke Leia # I've set variable 'Luke' to value 'Hello from Leia'
echo "$Luke" # Hello from Leia
Whether you should use these approaches is a question. Generally, immutable code is easier to read and to reason about (in almost any programming language). However, sometimes you really need to get stuff done in a certain way. Hope this answer helps you then.

Shell Script: Is there a difference between "local foo" and "local foo="?

I found following code in /etc/init.d/functions on CentOS.
status() {
local base pid lock_file= pid_file=
...
4 variables are declared.
Two of them are not initialized, base and pid.
But rest of them are initialized with empty value, lock_file and pid_file.
I tested following code and found no differences.
local a b=
echo "a is $a, length is ${#a}"
echo "b is $b, length is ${#b}"
Is there any differences between them?
Yes, there is a difference. Consider the following function:
x() {
local a b=
echo ${a-X}
echo ${b-X}
}
Calling this function in bash-4.x results in this output:
$ x
X
$
The ${parameter−word} parameter expansion expands to the expansion of word (in this case X) if the parameter is unset, or to the parameter value if it is set.
From the example output, it is obvious that local a leaves the variable a unset, while local b= explicitly sets it to the empty (null) string.
EDIT:
On the other hand, on bash-3.x you get this:
$ x
$
A call to set within the function verifies that local a in bash-3.x initializes that variable to the empty string. This, however, seems to have been a bug. From the bash changelog:
This document details the changes between this version, bash-4.0-beta,
and the previous version, bash-4.0-alpha.
...
e. Fixed a bug that caused local variables to be created with the empty
string for a value rather than no value.

shell script function return a string

I am new to shell scripts, I am trying to create a simple function which will return the concatenated two strings that are passed as parameters. I tried with below code
function getConcatenatedString() {
echo "String1 $1"
echo "String2 $2"
str=$1/$2
echo "Concatenated String ${str}"
echo "${str}"
}
//I am calling the above function
constr=$(getConcatenatedString "hello" "world")
echo "printing result"
echo "${constr}"
echo "exit"
I see the below output when running the script with above code,
printing result
String1 hello
String2 world
Concatenated String hello/world
hello/world
exit
If you look at the code I am first calling the function and then I am echoing "printing result" statement, but the result is first comes the "printing result" and echos the statement inside the function. Is the below statement calling the function
constr=$(getConcatenatedString "hello" "world")
or
echo ${constr}
is calling the function ?
Because if I comment out #echo ${constr} then nothing is getting echoed !!! Please clarify me.
The first is calling the function and storing all of the output (four echo statements) into $constr.
Then, after return, you echo the preamble printing result, $constr (consisting of four lines) and the exit message.
That's how $() works, it captures the entire standard output from the enclosed command.
It sounds like you want to see some of the echo statements on the console rather than capturing them with the $(). I think you should just be able to send them to standard error for that:
echo "String1 $1" >&2
paxdiablo's solution is correct. You cannot return a string from a function, but you can capture the output of the function or return an integer value that can be retrieved by the caller from $?. However, since all shell variables are global, you can simply do:
getConcatenatedString() { str="$1/$2"; }
getConcatenatedString hello world
echo "Concatenated String ${str}"
Note that the function keyword is redundant with (), but function is less portable.
A more flexible, but slightly harder to understand approach is to pass a variable name, and use eval so that the variable becomes set in the caller's context (either a global or a function local). In bash:
function mylist()
{
local _varname=$1 _p _t
shift
for _p in "$#"; do
_t=$_t[$_p]
done
eval "$_varname=\$_t"
}
mylist tmpvar a b c
echo "result: $tmpvar"
On my Linux desktop (bash-3.2) it's approx 3-5x faster (10,000 iterations) than using ``, since the latter has process creation overheads.
If you have bash-4.2, its declare -g allows a function to set a global variable, so you can replace the unpretty eval with:
declare -g $_varname="$_t"
The eval method is similar to TCL's upvar 1, and declare -g is similar to upvar #0.
Some shell builtins support something similar, like bash's printf with "-v", again saving process creation by assigning directly to a variable instead of capturing output (~20-25x faster for me).

ksh: assigning function output to an array

Why doesn't this work???
#!/bin/ksh
# array testfunc()
function testfunc {
typeset -A env
env=( one="motherload" )
print -r $env
return 0
}
testfunc # returns: ( one=motherload )
typeset -A testvar # segfaults on linux, memfaults on solaris
testvar=$(testfunc) # segfaults on linux, memfaults on solaris
print ${testvar.one}
note: I updated the above script to print ${testvar.one} from print $testvar to show more precisely what I am trying to accomplish.
I am sure this has been asked before, but I am not sure what to search on and everything I have been trying to use for keywords is not bringing me any answers that relate to my problem.
ksh version:
linux: version sh (AT&T Research) 1993-12-28 s+
solaris: version sh (AT&T Research) 93s+ 2008-01-31
Update:
So another question is, this will run in ksh 93t+ without giving an error, but, it doesn't assign the array properly. I would I go about assigning an array from a function? I tried assigning the array like this also:
typeset -A testvar=$(testfunc)
print ${testvar.one}
But that also didn't work properly.
EDIT
So what is happening here?
typeset -A env=( one="motherload" two="vain" )
print ${env.one}
print ${env.two}
I thought this was how you defined associative arrays, maybe what I was looking at was old but who knows.... seems odd behaviour since this prints out "motherload" and "vain"
Your script works fine for me on Linux with ksh 93t+.
Since it's the same script and you're getting similar errors in two different environments, I would suspect stray characters in the file. Try one of these to show any stray characters that might be present:
hd filename
cat -v filename
hexdump -C filename
If it's simply a matter of DOS line endings, then this will fix that:
dos2unix filename
Edit:
Here's one way to create and populate an associative array in ksh:
$ typeset -A testvar
$ testvar=([one]="motherlode" [two]="vein" [waste]="tailings")
$ echo ${testvar[two]}
vein
$ testvar[ore]="gold"
$ echo ${!testvar[#]} # print the indices of the array
one two waste ore
$ typeset -p testvar # show the current definition of the array
typeset -A testvar=([one]="motherlode" [two]="vein" [waste]="tailings" [ore]="gold")
As you can see, ksh uses bracketed subscripts for arrays. Dotted notation is used for accessing members of a compound variable.
I don't believe ksh functions can return arrays. However, you can use the print technique you have in your function (but add square brackets around the index name) and use eval to do the assignment.
$ typeset -A testvar
$ eval "testvar=($(testfunc))"
or to append to an existing array:
$ eval "testvar+=($(testfunc))"
Unless your function is using associative arrays internally, you don't necessarily need to use them to build your output.
However, if you do, you can parse from the result of typeset -p:
$ result=$(typeset -p env)
$ result=${result#*\(}
$ result=${result%\)*}
$ print result
or iterate through the array:
$ for index in ${!env[#]}; do print -n "[$index]=${env[$index]} "; done; print
You may want to consult the documentation concerning discipline functions and type variables
Here is an alternative to getting any return value from a function using name reference. The value returned will be stored in a variable defined as the first positional argument of the function (not declaring the variable beforehand will work but the variable will be global):
#################################
# Example using compound variable
#################################
function returnCompound {
typeset -n returnVal="$1"
returnVal=( one="motherloadCompound" )
return 0
}
# Declaring the variable to keep it in this scope
# Useful for calling nested functions whitout messing
# with the global scope
typeset myNewCompoundVar
returnCompound myNewCompoundVar
echo "Compound: ${myNewCompoundVar.one}"
##################################
# Example using asssociative array
##################################
function returnMap {
typeset -n myNewMapVar="$1"
myNewMapVar=( [one]="motherloadMap" )
typeset nestedCompoundVar
returnCompound nestedCompoundVar
echo "Compound (Nested) from inside: ${nestedCompoundVar.one}"
return 0
}
# Declaring the variable to keep it in this scope
# Useful for calling nested functions whitout messing
# with the global scope
typeset myNewMapVar
returnMap myNewMapVar
echo "Associative array: ${myNewMapVar[one]}"
echo "Compound (Nested) from outside: ${nestedCompoundVar.one}"
Output:
Compound: motherloadCompound
Compound (Nested) from inside: motherloadCompound
Associative array: motherloadMap
Compound (Nested) from outside:
Important side notes:
Function declarations must be done using the function keyword or else the concept of local scope variable won't be taken under account. In which case the name of your reference variable and global variable might clash if they happen to be the same, resulting in a typeset: invalid self reference error. This can be tested by changing the declaration of the 'returnMap' function.
If you do not declare the return variable before the function call, the variable to which is assigned the return value will be created globally and not limited to the calling scope.

KornShell Printf - Padding a string

I'm attempting to write a KornShell (ksh) function that uses printf to pad a string to a certain width.
Examples:
Call
padSpaces Hello 10
Output
'Hello '
I currently have:
padSpaces(){
WIDTH=$2
FORMAT="%-${WIDTH}.${WIDTH}s"
printf $FORMAT $1
}
Edit: This seems to be working, in and of itself, but when I assign this in the script it seems to lose all but the first space.
TEXT=`padSpaces "TEST" 10`
TEXT="${TEXT}A"
echo ${TEXT}
Output:
TEST A
I'm also open to suggestions that don't use printf. What I'm really trying to get at is a way to make a fixed width file from ksh.
Your function works fine for me. Your assignment won't work with spaces around the equal sign. It should be:
SOME_STRING=$(padSpaces TEST 10)
I took the liberty of replacing the backticks, too.
You don't show how you are using the variable or how you obtain the output you showed. However, your problem may be that you need to quote your variables. Here's a demonstration:
$ SOME_STRING=$(padSpaces TEST 10)
$ sq=\'
$ echo $sq$SOME_STRING$sq
'TEST '
$ echo "$sq$SOME_STRING$sq"
'TEST '
Are you aware that you define a function called padSpaces, yet call one named padString? Anyway, try this:
padString() {
WIDTH=$2
FORMAT="%-${WIDTH}s"
printf $FORMAT $1
}
Or, the more compact:
padString() {
printf "%-${2}s" $1
}
The minus sign tells printf to left align (instead of the default right alignment). As the manpage states about the command printf format [ arg ... ],
The arguments arg are printed on standard output in accordance with the
ANSI-C formatting rules associated with the format string format.
(I just installed ksh to test this code; it works on my machineTM.)

Resources