DaemonForums  

Go Back   DaemonForums > Miscellaneous > Programming

Programming C, bash, Python, Perl, PHP, Java, you name it.

Reply
 
Thread Tools Display Modes
  #1   (View Single Post)  
Old 27th January 2010
J65nko J65nko is offline
Administrator
 
Join Date: May 2008
Location: Budel - the Netherlands
Posts: 4,125
Default Dereferencing sh variables

In a Linux Journal Tech tip is shown how you can dereference a variable, when it is passed as string to a function. It uses the bash ! operator.

Code:
DerefernceVariablePassedToFunction() {
    if [ -n "$1" ] ; then
        echo "value of [${1}] is: [${!1}]"
    else
        echo "Null parameter passed to this function"
    fi
}

Variable="LinuxJournal"
DerefernceVariablePassedToFunction Variable
This will now print LinuxJournal.

I just wondered why he did not use eval like this
Code:
#/bin/sh

dereference() {
    if [ -n "$1" ] ; then
        eval echo value of \$1 is: \$$1
    else
        echo "Null parameter passed to this function"
    fi
}

variable="Just use 'eval'"
dereference variable
This has the same effect. A run with sh -vx so you can see the double interpretation pass performed by eval
Code:
 sh -vx dereference.sh 
#/bin/sh

dereference() {
    if [ -n "$1" ] ; then
        eval echo value of \$1 is: \$$1
    else
        echo "Null parameter passed to this function"
    fi
}

variable="Just use 'eval'"
+ variable=Just use 'eval'
dereference variable 
+ dereference variable
echo value of $1 is: $variable
value of variable is: Just use 'eval'
The first pass converts
echo value of \$1 is: \$$1
into
echo value of $1 is: $variable
\$1 is seen by the shell as a "$" followed by a '1' . The "\" prevents
the shell from interpreting $1 as the first parameter, as it normally would do.

\$$1is parsed as a literal "$" followed by the function parameter $1 which contains the string "variable"


The second and last pass re-processes this
echo value of $1 is: $variable
and without any "\" to prevent variable expansion, produces the wanted result:
value of variable is: Just use 'eval'
__________________
You don't need to be a genius to debug a pf.conf firewall ruleset, you just need the guts to run tcpdump
Reply With Quote
  #2   (View Single Post)  
Old 27th January 2010
TerryP's Avatar
TerryP TerryP is offline
Arp Constable
 
Join Date: May 2008
Location: USofA
Posts: 1,547
Default

It's linux journal, people assume, who ever said they considered real portability
__________________
My Journal

Thou shalt check the array bounds of all strings (indeed, all arrays), for surely where thou typest ``foo'' someone someday shall type ``supercalifragilisticexpialidocious''.
Reply With Quote
  #3   (View Single Post)  
Old 28th January 2010
J65nko J65nko is offline
Administrator
 
Join Date: May 2008
Location: Budel - the Netherlands
Posts: 4,125
Default

Another demonstration of eval , but where we encounter something strange.
Code:
#!/bin/sh

fr_1=un
fr_2=deux
fr_3=trois

nl_1=een
nl_2=twee
nl_3=drie

de_1=eins
de_2=zwei
de_3=drei

one2three() {
    local LANG
    LANG="$1"
    printf "\nCounting in : ${LANG}\n"
    for nr in 1 2 3 ; do
        eval printf "-%s" "\$$LANG_$nr"
    done
    printf "\n"
}

#typeset -ft one2three          # trace into function (for ksh)

one2three nl
one2three fr
one2three de
The output
Code:
$ 
./lang-eval  

Counting in : nl
-nl--

Counting in : fr
-fr--

Counting in : de
-de--
Not what we expect
A run with a verbose shell trace shows :
Code:
sh -vx lang-eval    
#!/bin/sh

fr_1=un
+ fr_1=un
fr_2=deux
+ fr_2=deux
fr_3=trois
+ fr_3=trois

nl_1=een
+ nl_1=een
nl_2=twee
+ nl_2=twee
nl_3=drie
+ nl_3=drie

de_1=eins
+ de_1=eins
de_2=zwei
+ de_2=zwei
de_3=drei
+ de_3=drei

one2three() {
    local LANG
    LANG="$1"
    printf "\nCounting in : ${LANG}\n"
    for nr in 1 2 3 ; do
        eval printf "-%s" "\$$LANG_$nr"
    done
    printf "\n"
}

#typeset -ft one2three          # trace into function (for ksh)

one2three nl
+ one2three nl

Counting in : nl
printf -%s $1
-nlprintf -%s $2
-printf -%s $3
-
one2three fr
+ one2three fr

Counting in : fr
printf -%s $1
-frprintf -%s $2
-printf -%s $3
-
one2three de
+ one2three de

Counting in : de
printf -%s $1
-deprintf -%s $2
-printf -%s $3
-
Somehow the variable "$LANG_$nr" is evaluated to a null string"" followed by the "$nr" variable. But why is the "_" not printed then?

Did you ever wonder why shell variables like "$name" are sometimes written as "${name}"?

Well the above phenomenom is the reason.

The underscore, "_", character is one of the valid characters in a variable name. So the shell seems to interpret "$LANG_", as a variable, which not defined anywhere in the script.
We only define "$LANG" as local variable in the shell function, not "$LANG_".

The solution is to use the curly braces to explicitly tell the shell where the variable name starts and ends.
Code:
eval printf "-%s" "\$${LANG}_$nr"
This produces the desired results
Code:
Counting in : nl
-een-twee-drie

Counting in : fr
-un-deux-trois

Counting in : de
-eins-zwei-drei
Now a verbose trace of this "fixed" version.
Because a "-vx" verbose tracing does not work inside a
function we first have to change
Code:
#typeset -ft one2three          # trace into function (for ksh)
into
Code:
typeset -ft one2three           # trace into function (for ksh)
The trace
Code:
./lang-eval  
+ typeset LANG
+ LANG=nl
+ printf \nCounting in : nl\n

Counting in : nl
+ eval printf -%s $nl_1
+ printf -%s een
-een+ eval printf -%s $nl_2
+ printf -%s twee
-twee+ eval printf -%s $nl_3
+ printf -%s drie
-drie+ printf \n

+ typeset LANG
+ LANG=fr
+ printf \nCounting in : fr\n

Counting in : fr
+ eval printf -%s $fr_1
+ printf -%s un
-un+ eval printf -%s $fr_2
+ printf -%s deux
-deux+ eval printf -%s $fr_3
+ printf -%s trois
-trois+ printf \n

+ typeset LANG
+ LANG=de
+ printf \nCounting in : de\n

Counting in : de
+ eval printf -%s $de_1
+ printf -%s eins
-eins+ eval printf -%s $de_2
+ printf -%s zwei
-zwei+ eval printf -%s $de_3
+ printf -%s drei
-drei+ printf \n
So we now can see, that in the first pass the "${LANG}" variable is expanded to "nl", "fr" and "de".
By using the curly braces "{}" , the shell does not have to guess anymore, which characters exactly make up the variable name end.

In most cases it will guess correctly, but in this case it really needs a little help from it's curly friends
__________________
You don't need to be a genius to debug a pf.conf firewall ruleset, you just need the guts to run tcpdump
Reply With Quote
  #4   (View Single Post)  
Old 29th January 2010
TerryP's Avatar
TerryP TerryP is offline
Arp Constable
 
Join Date: May 2008
Location: USofA
Posts: 1,547
Default

I've found it a good habit, to reach for ${} whenever there will be characters intermixed with the variable names, in my scripts, this usually manifests itself as "${ham}/${spam}-${eggs}" and such.


I figure, it beats debugging silly errors if things ever get changed into a situation that needs expicit, or worrying about the possibility of some unexpected variable slipping into the execution environment later, and changing the implied behaviour. Sometimes code that is explicit is better documentation then a comment.
__________________
My Journal

Thou shalt check the array bounds of all strings (indeed, all arrays), for surely where thou typest ``foo'' someone someday shall type ``supercalifragilisticexpialidocious''.
Reply With Quote
Reply

Tags
bash ! operator, curly braces, dereference, eval, variable name

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
passing make args/variables to builds of prerequisite ports jbhappy FreeBSD Ports and Packages 2 18th July 2008 02:35 PM


All times are GMT. The time now is 11:01 AM.


Powered by vBulletin® Version 3.8.4
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Content copyright © 2007-2010, the authors
Daemon image copyright ©1988, Marshall Kirk McKusick