although export gg I got unbound variable gg - linux

include.sh
#!/bin/bash -
export gg
f() {
for i in "${gg[#]}"
do
echo $i
done
}
run.sh
#!/bin/bash -
set -o nounset
. include.sh || exit 1
f
I get this error
scripts/include.sh: line 5: gg[#]: unbound variable
Isn't export keyword supposed to make gg global and available anywhere?
If not, how to make gg available everywhere from include.sh ?
UPDATE
Environment:
$ cat /etc/*-release
NAME="SLES"
VERSION="11.4"
VERSION_ID="11.4"
PRETTY_NAME="SUSE Linux Enterprise Server 11 SP4"

Legacy Unix shells don't support () arrays. You should invoke the scripts with bash and they'll run as expected.
As written in the updated question, you need to define gg before exporting it:
gg=()
export gg
I tested the patch and it works fine.

If you source a file (equivalent to .), the commands are executed in the current shell context, therefore there is no need for export. However, using unset/empty array will cause bash to terminate your script if you are using set -o nounset. Either assign some values to your array:
gg=(value1 value2) #can be assigned in both run.sh and/or include.sh
f() {
for i in "${gg[#]}"; do
echo "$i"
done
}
Or use parameter expansion to handle this:
f() {
for i in ${gg[#]+"${gg[#]}"}; do
echo "$i"
done
}
${parameter+word} will expand to word only if parameter is set, otherwise nothing is substituted. If you want to read more about how this works with arrays: wiki.bash-hackers.org.
As mentioned by #CharlesDuffy, in bash 4.4 empty array doesn't become an error. There is no error even without assignment array=(). For more information, see:
BashFAQ #112.

Related

Using "read" to set variables

In bash from the CLI I can do:
$ ERR_TYPE=$"OVERLOAD"
$ echo $ERR_TYPE
OVERLOAD
$ read ${ERR_TYPE}_ERROR
1234
$ echo $OVERLOAD_ERROR
1234
This works great to set my variable name dynamically; in a script it doesn't work. I tried:
#!/bin/env bash
ERR_TYPE=("${ERR_TYPE[#]}" "OVERLOAD" "PANIC" "FATAL")
for i in "${ERR_TYPE[#]}"
do
sh -c $(echo ${i}_ERROR=$"1234")
done
echo $OVERLOAD_ERROR # output is blank
# I also tried these:
# ${i}_ERROR=$(echo ${i}_ERROR=$"1234") # command not found
# read ${i}_ERROR=$(echo ${i}_ERROR=$"1234") # it never terminates
How would I set a variable as I do from CLI, but in a script? thanks
When you use dynamic variables names instead of associative arrays, you really need to question your approach.
err_type=("OVERLOAD" "PANIC" "FATAL")
declare -A error
for type in "${err_type[#]}"; do
error[$type]=1234
done
Nevertheless, in bash you'd use declare:
declare "${i}_error=1234"
Your approach fails because you spawn a new shell, passing the command OVERLOAD_ERROR=1234, and then the shell exits. Your current shell is not affected at all.
Get out of the habit of using ALLCAPSVARNAMES. One day you'll write PATH=... and then wonder why your script is broken.
If the variable will hold a number, you can use let.
#!/bin/bash
ERR_TYPE=("OVERLOAD" "PANIC" "FATAL")
j=0
for i in "${ERR_TYPE[#]}"
do
let ${i}_ERROR=1000+j++
done
echo $OVERLOAD_ERROR
echo $PANIC_ERROR
echo $FATAL_ERROR
This outputs:
1000
1001
1002
I'd use eval.
I think this would be considered bad practice though (it had some thing to do with the fact that eval is "evil" because it allows bad input or something):
eval "${i}_ERROR=1234"

In Bash, how do I stream the end of a pipeline into a variable?

I know that any variable set at the end of a pipeline is lost (excluding with the shell option in Bash 4 - unfortunately this needs to be a portable solution). However, I am sure there must be a way with file descriptors or something to stream the output of the end of a pipeline into a variable, even if via some convoluted route! :)
Ideally I want a command/function that takes one argument, the name of a variable that will eventually result in containing the output of the rest of the pipeline.
I have got a function that will find its next free file descriptor in a portable fashion:
getFd ()
{
# we'll start with 3 since 0..2 are mapped to standard in, out, and error respectively
local myFD='3'
# we'll get the upperbound from bash's ulimit
local FD_MAX=$( ulimit -n )
local FD_LOC
if [ -e /proc/$$/fd ]
then
FD_LOC="/proc/$$/fd"
elif [ -e /dev/fd ]
then
FD_LOC="/dev/fd"
else
return 1
fi
while [ -e "${FD_LOC}/${myFD}" ] && [ "${myFD}" -le "${FD_MAX}" ]
do
((++myFD))
done
eval FD="${myFD}"
}
I am thinking I might need to do something like previously creating a pool of open file descriptors that can be pulled in via some alias jiggery pokey or something, but am hoping that I am missing some much simpler way as am sure there must be a better way.
I was also thinking that if I added printf %s "${myFD}" at the end I could do something like alias '{FD}'="$( getFd )" to implement the Bash 4 feature of automatically finding the next available file descriptor for use in the form of {FD} <filename note: the need to have a space, but if this can be made to work it would be great to bring this feature to bash 3.0 for example. Also, would probably have to use shopt -s expand_aliases.
Any ideas would be greatly appreciated!
P.S. I am trying to avoid having to force the MyCommand MyVariable < <( command1 | command2 ; ) ; type syntax and and am striving if it is possible to end with the: $ command1 | command2 | MyCommand MyVariable ; type of use.
Your question is a bit confusing, but I guess you want to set a user-defined variable name to the 1st available file descriptor.
If that's the case, your function should simply echo the fd number:
getFD() {
...
echo $myFD
}
And then let's say foo contains the variable name to store the fd into, let's assume it contains the string bar:
eval $foo=`getFD`
After that variable bar will contain the first available fd.

Clean the '-x option' inside a script

I've been using "set -x" inside bash scripts in order to help me debug some functions, and it has been working very well for me
-x After expanding each simple command, for command, case command,
select command, or arithmetic for command, display the expanded
value of PS4, followed by the command and its expanded arguments or
associated word list.
However I'd like to be able to clear it before I leave the function
Eg:
#/bin bash
function somefunction()
{
set -x
# some code I'm debugging
# clear the set -x
set ????
}
somefunction
Quoting the manual:
Using + rather than - causes these flags to be turned off.
So it's set +x what you are looking for.
Consider a function like
foo () {
set -x
# do something
set +x
}
The problem is that if the -x option was already set before foo was called, it will be turned off by foo.
If you want to restore the old value, you'll have to test whether it was enabled already using $-.
foo () {
[[ $- != *x* ]]; x_set=$? # 1 if already set, 0 otherwise
set -x
# do something
(( x_set )) || set +x # Turn off -x if it was off before
}
For some more info always refer to the basic guide. This clearly gives you the answer :
http://www.tldp.org/LDP/Bash-Beginners-Guide/html/Bash-Beginners-Guide.html
set -x # activate debugging from here
w
set +x # stop debugging from here

Bash config file or command line parameters

If I am writing a bash script, and I choose to use a config file for parameters. Can I still pass in parameters for it via the command line? I guess I'm asking can I do both on the same command?
The watered down code:
#!/bin/bash
source builder.conf
function xmitBuildFile {
for IP in "{SERVER_LIST[#]}"
do
echo $1#$IP
done
}
xmitBuildFile
builder.conf:
SERVER_LIST=( 192.168.2.119 10.20.205.67 )
$bash> ./builder.sh myname
My expected output should be myname#192.168.2.119 and myname#10.20.205.67, but when I do an $ echo $#, I am getting 0, even when I passed in 'myname' on the command line.
Assuming the "config file" is just a piece of shell sourced into the main script (usually containing definitions of some variables), like this:
. /etc/script.conf
of course you can use the positional parameters anywhere (before or after ". /etc/..."):
echo "$#"
test -n "$1" && ...
you can even define them in the script or in the very same config file:
test $# = 0 && set -- a b c
Yes, you can. Furthemore, it depends on your architecture of script. You can overwrite parametrs with values from config and vice versa.
By the way shflags may be pretty useful in writing such script.

Accessing variable from ARGV

I'm writing a cPanel postwwwact script, if you're not familiar with the script its run after a new account is created. it relies on the user account variable being passed to the script which i then use for various things (creating databases etc). However, I can't seem to find the right way to access the variable i want. I'm not that good with shell scripts so i'd appreciate some advice. I had read somewhere that the value i wanted would be included in $ARGV{'user'} but this simply gives "root" as opposed to the value i need. I've tried looping through all the arguments (list of arguments here) like this:
#!/bin/sh
for var
do
touch /root/testvars/$var
done
and the value i want is in there, i'm just not sure how to accurately target it. There's info here on doing this with PHP or Perl but i have to do this as a shell script.
EDIT Ideally i would like to be able to call the variable by something other than $1 or $2 etc as this would create issues if an argument is added or removed
..for example in the PHP code here:
function argv2array ($argv) {
$opts = array();
$argv0 = array_shift($argv);
while(count($argv)) {
$key = array_shift($argv);
$value = array_shift($argv);
$opts[$key] = $value;
}
return $opts;
}
// allows you to do the following:
$opts = argv2array($argv);
echo $opts[‘user’];
Any ideas?
The parameters are passed to your script as a hash:
/scripts/$hookname user $user password $password
You can use associative arrays in Bash 4, or in earlier versions of Bash you can use built up variable names.
#!/bin/bash
# Bash >= 4
declare -A argv
for ((i=1;i<=${##};i+=2))
do
argv[${#:i:1}]="${#:$((i+1)):1}"
done
echo ${argv['user']}
Or
#!/bin/bash
# Bash < 4
for ((i=1;i<=${##};i+=2))
do
declare ARGV${#:i:1}="${#:$((i+1)):1}"
done
echo ${!ARGV*} # outputs all variable names that begin with ARGV
echo $ARGVuser
Running either:
$ ./argvtest user dennis password secret
dennis
Note: you can also use shift to step through the arguments, but it's destructive and the methods above leave $# ($1, $2, etc.) in place.
#!/bin/bash
# Bash < 4
# using shift (can use in Bash 4, also)
for ((i=1;i<=${##}+2;i++))
do
declare ARGV$1="$2"
# Bash 4: argv[$1}]="$2"
shift 2
done
echo ${!ARGV*}
echo $ARGVuser
If it's passed as a command-line parameter to the script, it's available as $1 if it's first parameter, $2 for the second, and so on.
Why not start off your script with something like
ARG_USER=$1
ARG_FOO=$2
ARG_BAR=$3
And then later in your script refer to $ARG_USER, $ARG_FOO and $ARG_BAR instead of $1, $2, and $3. That way, if you decide to change the order of arguments, or insert a new argument somewhere other than at the end, there is only one place in your code that you need to update the association between argument order and argument meaning.
You could even do more complex processing of $* to set your $ARG_WHATEVER variables, if it's not always going to be that all of the are specified in the same order every time.
You can do the following:
#!/bin/bash
for var in $argv; do
<do whatver you want with $var>
done
And then, invoke the script as:
$ /path/to/script param1 arg2 item3 item4 etc

Resources