Set a Bash function on the environment - linux

I need to define a Bash function in the Bash environment from a C/C++ program. Before the shellshock bug, I could define a function in this way:
my_func='() { echo "This is my function";}'
Or equivalent from a C program:
setenv("my_func", "() { echo \"This is my function\";}", 1);
Or
putenv("my_func=() { echo \"This is my function\";}");
But using a Bash version with shellshock fixed, I can't manage on how to define my functions in the environment.
The strange thing is, if I run env, I can see my function defined in the environment, but if I call it, Bash says that it doesn't exist.
Thanks in advance

For informational purposes only. Since it is not documented how functions are exported to the environment, you should treat this as an abuse of a private API that is subject to change in future versions of bash.
Functions are no longer exported using simply the name of the function in the environment string. To see this, run
$ my_func () { echo "foo"; }
$ export -f my_func
$ env | grep -A1 'my_func'
BASH_FUNC_my_func%%=() { echo "foo"
}
Since the name used in the environment is no longer a valid bash identifier, you would need to use the env command to modify the environment of the new process.
env 'BASH_FUNC_my_func%%=() { echo "This is my function"; }' bash
From C, you just need to adjust the name.​​​​​​
setenv("BASH_FUNC_my_func%%", "() { echo \"This is my function\";}", 1);

If you are invoking bash with execv (so that you are only invoking it once), you could replace (using execl for explanatory purposes):
execl("/bin/bash", "bash", "file_to_run", "arg1", "arg2", 0);
with
execl("/bin/bash", "bash", "-c", "f() {...} g() {...}\n. $0",
"file_to_run", "arg1", "arg2", 0);
and then you don't need to play games with the internal bash interface for defining functions. (If the bash script being run also needs the functions to be exported, for whatever reason, just add export -f <func> lines to the argument following -c.)
That has the advantage of working with both patched and unpatched bashes.
(I'm having to make a similar patch to various programs, so I share your pain.)

Related

although export gg I got unbound variable gg

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.

BASH: reset the variables used by certain script

A trivial situation - the script has finished it's execution, and all the variables used on it's way remained.
I'm looking for a way the script could unset all used by it variables ONLY, as there are many other scripts setting their stuff...
'exec bash' is not an option.
EG from my imagination:
function setVariables {
A="~/"
B=$(du -sh /smth)
C="tralala"
}
setVariables
function cleanup {
readarray -t args < <(setVariables)
set -u "${args[#]}"
}
cleanup
How to achieve this?
Like already suggested in comments, the trivial solution is to put the commands in a script instead of in a function. The script runs in a subprocess with its own environment, and doesn't change anything in the parent. When the child finishes, nothing from its environment is left.
If you need to use a function, Bash has a local keyword which makes the variable's scope local to the current function. When the function ends, all variables are reset to their state from before the function ran (unset if they were unset, previous value if they were set).
If you want to set variables and have them set outside of a function, but have a simple way to revert them to their original value, you really have to build that yourself. Look at how Python's virtualenv does it, for example (a common and popular example, though not necessarily the absolutely most robust) -- it basically writes out the previous values to the definition of the deactivate command which disables the virtual environment and (attempts to) return things to the way they were before you ran activate.
# bash 4+ only!
setVariables () {
declare -A _setvariables_old=([A]="$A" [B]="$B" [C]="$C")
A="~/"
B=$(du -sh /smth)
C="tralala"
}
setVariables
:
cleanup () {
local var
for var in "${!_setvariables_old[#]}"; do
printf -v "$var" "%s" "${_setvariables_old[$var]}"
done
unset _setvariables_old
}
This leaks slightly (what if _setvariables_old is a variable you want to preserve!?) and doesn't know how to unset things; it just reverts to an empty string if something was unset before you started.

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.

How can I run a function from a script in command line?

I have a script that has some functions.
Can I run one of the function directly from command line?
Something like this?
myScript.sh func()
Well, while the other answers are right - you can certainly do something else: if you have access to the bash script, you can modify it, and simply place at the end the special parameter "$#" - which will expand to the arguments of the command line you specify, and since it's "alone" the shell will try to call them verbatim; and here you could specify the function name as the first argument. Example:
$ cat test.sh
testA() {
echo "TEST A $1";
}
testB() {
echo "TEST B $2";
}
"$#"
$ bash test.sh
$ bash test.sh testA
TEST A
$ bash test.sh testA arg1 arg2
TEST A arg1
$ bash test.sh testB arg1 arg2
TEST B arg2
For polish, you can first verify that the command exists and is a function:
# Check if the function exists (bash specific)
if declare -f "$1" > /dev/null
then
# call arguments verbatim
"$#"
else
# Show a helpful error
echo "'$1' is not a known function name" >&2
exit 1
fi
If the script only defines the functions and does nothing else, you can first execute the script within the context of the current shell using the source or . command and then simply call the function. See help source for more information.
The following command first registers the function in the context, then calls it:
. ./myScript.sh && function_name
Briefly, no.
You can import all of the functions in the script into your environment with source (help source for details), which will then allow you to call them. This also has the effect of executing the script, so take care.
There is no way to call a function from a shell script as if it were a shared library.
Using case
#!/bin/bash
fun1 () {
echo "run function1"
[[ "$#" ]] && echo "options: $#"
}
fun2 () {
echo "run function2"
[[ "$#" ]] && echo "options: $#"
}
case $1 in
fun1) "$#"; exit;;
fun2) "$#"; exit;;
esac
fun1
fun2
This script will run functions fun1 and fun2 but if you start it with option
fun1 or fun2 it'll only run given function with args(if provided) and exit.
Usage
$ ./test
run function1
run function2
$ ./test fun2 a b c
run function2
options: a b c
I have a situation where I need a function from bash script which must not be executed before (e.g. by source) and the problem with #$ is that myScript.sh is then run twice, it seems... So I've come up with the idea to get the function out with sed:
sed -n "/^func ()/,/^}/p" myScript.sh
And to execute it at the time I need it, I put it in a file and use source:
sed -n "/^func ()/,/^}/p" myScript.sh > func.sh; source func.sh; rm func.sh
Edit: WARNING - seems this doesn't work in all cases, but works well on many public scripts.
If you have a bash script called "control" and inside it you have a function called "build":
function build() {
...
}
Then you can call it like this (from the directory where it is):
./control build
If it's inside another folder, that would make it:
another_folder/control build
If your file is called "control.sh", that would accordingly make the function callable like this:
./control.sh build
Solved post but I'd like to mention my preferred solution. Namely, define a generic one-liner script eval_func.sh:
#!/bin/bash
source $1 && shift && "#a"
Then call any function within any script via:
./eval_func.sh <any script> <any function> <any args>...
An issue I ran into with the accepted solution is that when sourcing my function-containing script within another script, the arguments of the latter would be evaluated by the former, causing an error.
The other answers here are nice, and much appreciated, but often I don't want to source the script in the session (which reads and executes the file in your current shell) or modify it directly.
I find it more convenient to write a one or two line 'bootstrap' file and run that. Makes testing the main script easier, doesn't have side effects on your shell session, and as a bonus you can load things that simulate other environments for testing. Example...
# breakfast.sh
make_donuts() {
echo 'donuts!'
}
make_bagels() {
echo 'bagels!'
}
# bootstrap.sh
source 'breakfast.sh'
make_donuts
Now just run ./bootstrap.sh.Same idea works with your python, ruby, or whatever scripts.
Why useful? Let's say you complicated your life for some reason, and your script may find itself in different environments with different states present. For example, either your terminal session, or a cloud provider's cool new thing. You also want to test cloud things in terminal, using simple methods. No worries, your bootstrap can load elementary state for you.
# breakfast.sh
# Now it has to do slightly different things
# depending on where the script lives!
make_donuts() {
if [[ $AWS_ENV_VAR ]]
then
echo '/donuts'
elif [[ $AZURE_ENV_VAR ]]
then
echo '\donuts'
else
echo '/keto_diet'
fi
}
If you let your bootstrap thing take an argument, you can load different state for your function to chew, still with one line in the shell session:
# bootstrap.sh
source 'breakfast.sh'
case $1 in
AWS)
AWS_ENV_VAR="arn::mumbo:jumbo:12345"
;;
AZURE)
AZURE_ENV_VAR="cloud::woo:_impress"
;;
esac
make_donuts # You could use $2 here to name the function you wanna, but careful if evaluating directly.
In terminal session you're just entering:
./bootstrap.sh AWS
Result:
# /donuts
you can call function from command line argument like below
function irfan() {
echo "Irfan khan"
date
hostname
}
function config() {
ifconfig
echo "hey"
}
$1
Once you defined the functions put $1 at the end to accept argument which function you want to call.
Lets say the above code is saved in fun.sh. Now you can call the functions like ./fun.sh irfan & ./fun.sh config in command line.

Bash - error message 'Syntax error: "(" unexpected'

For some reason, this function is working properly. The terminal is outputting
newbootstrap.sh: 2: Syntax error: "(" unexpected
Here is my code (line 2 is function MoveToTarget() {)
#!/bin/bash
function MoveToTarget() {
# This takes two arguments: source and target
cp -r -f "$1" "$2"
rm -r -f "$1"
}
function WaitForProcessToEnd() {
# This takes one argument. The PID to wait for
# Unlike the AutoIt version, this sleeps for one second
while [ $(kill -0 "$1") ]; do
sleep 1
done
}
function RunApplication() {
# This takes one application, the path to the thing to execute
exec "$1"
}
# Our main code block
pid="$1"
SourcePath="$2"
DestPath="$3"
ToExecute="$4"
WaitForProcessToEnd $pid
MoveToTarget $SourcePath, $DestPath
RunApplication $ToExecute
exit
You're using the wrong syntax to declare functions. Use this instead:
MoveToTarget() {
# Function
}
Or this:
function MoveToTarget {
# function
}
But not both.
Also, I see that later on you use commas to separate arguments (MoveToTarget $SourcePath, $DestPath). That is also a problem. Bash uses spaces to separate arguments, not commas. Remove the comma and you should be golden.
I'm also new to defining functions in Bash scripts. I'm using a Bash of version 4.3.11(1):-release (x86_64-pc-linux-gnu) on Ubuntu 14.04 (Trusty Tahr).
I don't know why, but the definition that starts with the keyword function never works for me.
A definition like the following
function check_and_start {
echo Hello
}
produces the error message:
Syntax error: "}" unexpected
If I put the { on a new line like:
function my_function
{
echo Hello.
}
It prints a Hello. when I run the script, even if I don't call this function at all, which is also what we want.
I don't know why this wouldn't work, because I also looked at many tutorials and they all put the open curly brace at the end of the first line. Maybe it's the version of Bash that we use?? Anyway, just put it here for your information.
I have to use the C-style function definition:
check_and_start() {
echo $1
}
check_and_start World!
check_and_start Hello,\ World!
and it works as expected.
If you encounter "Syntax error: "(" unexpected", then use "bash" instead of using "sh".
For example:
bash install.sh
I had the same issue. I was running scripts on Ubuntu sometimes using sh vs. Dash. It seems running scripts with sh causes the issue, but running scripts with Dash works fine.

Resources