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.
Related
I'm making a thing which has to source
a POSIX shell script (NOT BASH) as its configuration
file, but it should only have definitions of variables,
functions and comments, nothing else like running something in
global scope, so I wonder, is there a way to
stop users from being able to run any code globally,
only in functions and stuff?
I mean:
main.sh
# ...
# . in POSIX sh has the same meaning as "source" in bash
. /etc/something/config.sh
echo "$(get_hello), $ME"
# ...
/etc/something/config.sh
#!/usr/bin/env sh
# This one is fine
export ME='Some user'
hello='hi :)'
# This is fine too
get_hello() {
echo '$hello'
}
# This should error or be ignored
echo 'I should not be executed/should throw an error'
Any way to do this?
I have a task to monitor the system with a quota, if the monitored result is over the quota, send a warning email. But this monitor program should be called once in half an hour, after one warning email is sent out, the next time if the monitored state is still the same as last time, there is no need to send the same warning email again.
In order to do this, I would like to make use of environment variable to store the state of the last monitored result, so that the next time it can be checked and duplicate email would not be sent. One of my solution is to add or update the export syntax in .bashrc, but in order to activate the updated export syntax, I have to run bash, which might be unnecessary.
So I would like ask is there any way to update the environment variable so that every time when the monitor program Bash script is called, it gets the fresh updated value?
This is a self contained solution using a heredoc. At first glance it may seem an elaborate and inperfect solution, it does have its uses in that it's resilient and it works well when deploying across more than one machine, requires no special monitoring or permissions of external files, and most importantly, there are no unwanted surprises with environment.
This example uses bash, but it will work with sh if the $thisfile variable is set manually, or another way.
This example assumes that 20 is already in the script file as mymonitorvalue, and uses argument $1 as a proof of concept. You would obviously change newval="$1" to whatever calculates the quota:
Example usage:
#bash $>./script 10
Value from previous run was 20
Value from this test was 10
Updating value ...
#bash $>./script 10
not doing anything ... result is the same as last time
#bash $>
Script:
#!/bin/bash
thisfile="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ; thisfile="${DIR}${0}"
read -d '' currentvalue <<'EOF'
mymonitorval=20
EOF
eval "$currentvalue"
function changeval () {
sed -E -i "s/(^mymonitorval\=)(.*)/mymonitorval\="$1"/g" "$thisfile"
}
newvalue="$1"
if [[ "$newvalue" != "$mymonitorval" ]]; then
echo "Value from previous run was $mymonitorval"
echo "Value from this test was "$1""
echo "Updating value ..."
changeval "$newvalue"
else
echo "not doing anything ... result is the same as last time"
fi
Explanation:
thisfile= can be set manually for script location. This example uses the automated solution from here: https://stackoverflow.com/a/246128
read -d...EOF is the heredoc which is saved into variable $currentvalue
eval "$currentvalue" in this case is the equivalent of typing mymonitorval=10 into a terminal
function changeval...} updates the contents of the heredoc in place (it changes the physical .sh script file)
newvalue="$1" is for the purpose of testing. $newvalue would be determined by whatever your script is that is calculating quota
if.... block is to perform two alternate sets of actions depending on whether $newvalue is the same as it was last time or not.
Store environment variable in different .file and then source <.file>
I'm not sure why this isn't working, but perhaps I've oversimplified/overcomplicated things
I'm writing a Perl script that ultimately needs to call an external program. The catch is, this program needs a modified version of the LD_LIBRARY_PATH environment variable, in order to find a couple of libraries which the vendor does not install in standard places.
OK, the environment is in %ENV, which can be rewritten, yes?
I thought if I changed LD_LIBRARY_PATH in the parent, it would affect the dynamic linking of the child.
So I have:
use Env qw(#LD_LIBRARY_PATH);
use IPC::System::Simple qw(capturex $EXITVAL);
# We need these to establish the call to rsq later
my ($rsqexe, $rsqhome, $suffix) = fileparse($config->rsq());
push #LD_LIBRARY_PATH, $rsqhome;
eval {
$output = capturex(
$config->rsq(),
qq/"$source"/
);
};
But the child process dies with an error indicating the shared libraries can't be found.
How can I improve this?
I do need to examine the contents of $output after successful execution.
eval {
$output = capturex(
$config->rsq(),
qq/"$source"/
);
};
Here's the problem: I wasn't examining what was in $# (or $EVAL_ERR if you use ENGLISH;)
If I had, I would have seen that the problem was with the quoting qq/"$source"/ - because capturex() doesn't call the shell (which was the desired behaviour) the quotes break the file name (i.e. test.pdf exists, but ""test.pdf"" does not).
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.)
This question already has answers here:
What's a concise way to check that environment variables are set in a Unix shell script?
(14 answers)
Closed 9 years ago.
I am writing a shell script, where I have to check if environment variable is set, if not set then I have to set it. Is there any way to check in shell script, whether an environment variable is already set or not ?
The standard solution to conditionally assign a variable (whether in the environment or not) is:
: ${VAR=foo}
That will set VAR to the value "foo" only if it is unset.
To set VAR to "foo" if VAR is unset or the empty string, use:
: ${VAR:=foo}
To put VAR in the environment, follow up with:
export VAR
You can also do export VAR=${VAR-foo} or export VAR=${VAR:=foo}, but some older shells do not support the syntax of assignment and export in the same line. Also, DRY; using the name on both sides of the = operator is unnecessary repetition. (A second line exporting the variable violates the same principal, but feels better.)
Note that it is very difficult in general to determine if a variable is in the environment. Parsing the output of env will not work. Consider:
export foo='
VAR=var-value'
env | grep VAR
Nor does it work to spawn a subshell and test:
sh -c 'echo $VAR'
That would indicate the VAR is set in the subshell, which would be an indicator that VAR is in the environment of the current process, but it may simply be that VAR is set in the initialization of the subshell. Functionally, however, the result is the same as if VAR is in the environment. Fortunately, you do not usually care if VAR is in the environment or not. If you need it there, put it there. If you need it out, take it out.
[ -z "$VARIABLE" ] && VARIABLE="abc"
if env | grep -q ^VARIABLE=
then
echo env variable is already exported
else
echo env variable was not exported, but now it is
export VARIABLE
fi
I want to stress that [ -z $VARIABLE ] is not enough, because you can have VARIABLE but it was not exported. That means that it is not an environment variable at all.
What you want to do is native in bash, it is called parameter substitution:
VARIABLE="${VARIABLE:=abc}"
If VARIABLE is not set, right hand side will be equal to abc. Note that the internal operator := may be replaced with :- which tests if VARIABLE is not set or empty.
if [ -z "$VARIABLE" ]; then
VARIABLE=...
fi
This checks if the length of $VARIABLE is zero.