Use result from mongodb in shell script - bash

I am trying to use the result printed from a parameterized MongoDB script file in a bash script.
The call looks like this:
mongo --quiet server/db --eval "a='b'" mongoscript.js
Inside mongoscript.js there is a print statement that prints the value 'foo' I want to use in my shell script. The problem is that when I execute above statement I get:
b
foo
instead of just 'foo'
Thus, if I do
res=`mongo --quiet server/db --eval "a='b'" mongoscript.js`
res contains both lines.
I can of course solve this with
res=`mongo ... |tail -n 1`
but I am hoping there is a more general way to avoid this superfluous output.
Thanks!

The superfluous output is the result of your assignment of a='b', which displays the result of the assignment in this context.
If you add the var keyword for variable assignment, you shouldn't have any extra output (and can still use the variable a in your script):
$ mongo --quiet --eval "var a='b'" mongoscript.js
foo
You can see the same behaviour in the mongo shell:
> a='b'
b
> var a='b'
>

Related

Bash: Insert unescaped string/characters from variable into command

In bash (GNU bash, version 3.2.57), I would like to substitute the exact content of a variable (unescaped) into a following command.
To illustrate what I mean, given the following string variable:
s="2>&1 > /dev/null"
If I try to insert that exact string into a command:
bash --version $s || echo "will install bash"
(this command is just a simple example for the sake of the question)
The command actually executed looks like this:
bash --version '2>&1' '>' /dev/null
The inserted strings are escaped, which I don't want.
What I would like instead is to somehow insert the content of s, unescaped, into the executed command, so that the executed command is this one:
bash --version 2>&1 > /dev/null
How could I achieve that ?
How could I achieve that ?
Instead of a variable, use a function.
run_this_silent() {
"$#" 2>&1 >/dev/null
}
run_this_silent bash --version
It is not possible to store redirections in a variable without using eval (or the equivalent bash -c COMMAND), and eval is a bad solution in pretty much every case imaginable. If you want to unconditionally silence a command (or a hundred commands) it's better to explicitly add the redirects to each of them.

Execute awk output exactly inside of executed script

There is some similar topics, but this is slightly different.
I have database with names of scripts and parameters a. When I execute:
sqlite3 log/log.db "select name, a from result" | awk -F '|' '{printf("a[%s]=%s;\n",$1,$2);}'
I see:
a[inc.bash]=4.23198234894777e-06;
a[inc.c]=3.53343440279423e-10;
In my bash script I would like to use an associative array.
When I execute this code (coding by hand value of a[inc.bash]):
declare -A a
a[inc.bash]=4.23198234894777e-06;
echo ${a[inc.bash]}
It works correctly and print
4.23198234894777e-06
But I do not know, how to use output of first presented command with awk to assign values of key of associative array a declared in my script.
I want to execute code that is printed by awk inside of my script, but when I use something like $() or ``, it prints a error like this:
code:
declare -A a
$(sqlite3 log/log.db "select name, a from result" | awk -F '|' '{printf("a[%s]=%s;\n",$1,$2);}')
echo ${a[inc.bash]}
output:
a[inc.bash]=4.23198234894777e-06; not found command
To tell Bash to interpret your output as commands, you can use process substitution and the source command:
declare -A a
source <(sqlite3 log/log.db "select name, a from result" |
awk -F '|' '{printf("a[%s]=%s;\n",$1,$2);}')
echo ${a[inc.bash]}
The <() construct (process substitution) can be treated like a file, and source (or the equivalent .) runs the commands in its argument without creating a subshell, making the resulting a array accessible in the current shell.
A simplified example to demonstrate, as I don't have your database:
$ declare -A a
$ source <(echo 'a[inc.bash]=value')
$ echo "${a[inc.bash]}"
value
This all being said, this is about as dangerous as using eval: whatever the output of your sqlite/awk script, it will be executed!

Using spaces in bash scripts

I have a script foo.sh
CMD='export FOO="BAR"'
$CMD
echo $FOO
It works as expected
>./foo.sh
"BAR"
Now I want to change FOO variable to BAR BAR. So I get script
CMD='export FOO="BAR BAR"'
$CMD
echo $FOO
When I run it I expect to get "BAR BAR", but I get
./foo.sh: line 2: export: `BAR"': not a valid identifier
"BAR
How I can deal with that?
You should not use a variable as a command by just calling it (like in your $CMD). Instead, use eval to evaluate a command stored in a variable. Only by doing this, a true evaluation step with all the shell logic is performed:
eval "$CMD"
(And use double quotes to pass the command to eval.)
Just don't do that.
And read Bash FAQ #50
I'm trying to save a command so I can run it later without having to repeat it each time
If you want to put a command in a container for later use, use a
function. Variables hold data, functions hold code.
pingMe() {
ping -q -c1 "$HOSTNAME"
}
[...]
if pingMe; then ..
The proper way to do that is to use an array instead:
CMD=(export FOO="BAR BAR")
"${CMD[#]}"

Execute command parsed from a string containing arguments with spaces

I would like to execute a command which is given by a variable (Variable cmd in this example):
cmd="echo 'First argument'"
$cmd
Expected result would be:
First argument
BUT ... actual result is:
'First argument'
What? I don't understand why I can see single quotes in the output. After all, if the command (=content of variable $cmd) would be issued directly, then no quotes leak into the output, it behaves as desired:
$ echo 'First argument'
First argument
To illustrate what I am trying to achieve in real life: in my deploy script there is a code block like this (strongly simplified, but you get the point):
#!/bin/bash
function execute {
cmd=$1
echo "Executing $cmd ..."
# execute the command:
$cmd
}
VERSION=1.0.2
execute "git tag -a 'release-$VERSION'"
Now, Git would create a tag which contains single quotes:
git tag
'1.0.2'
which is not what I want ...
What to do?
(Bash version: GNU bash 3.1.0)
(I found a very similar issue, here, but the answer would not apply to my problem)
cmd="echo 'First argument'"
$cmd
What happens there is word splitting and the actual resulting command is:
echo "'First" "argument'"
Double-parsing with the single quotes inside would never happen.
Also, it's better to use arrays:
#!/bin/bash
function execute {
cmd=("$#") ## $# is already similar to an array and storing it to another is just optional.
echo "Executing ${cmd[*]} ..."
# execute the command:
"${cmd[#]}"
}
VERSION=1.0.2
execute git tag -a "release-$VERSION"
For eval is a difficult choice in that kind of situation. You may not only get unexpected parsing results, but also unexpectedly run dangerous commands.
I think this is what you want:
cmd="echo 'First arg'"
eval $cmd
First arg

bash: function + source + declare = boom

Here is a problem:
In my bash scripts I want to source several file with some checks, so I have:
if [ -r foo ] ; then
source foo
else
logger -t $0 -p crit "unable to source foo"
exit 1
fi
if [ -r bar ] ; then
source bar
else
logger -t $0 -p crit "unable to source bar"
exit 1
fi
# ... etc ...
Naively I tried to create a function that do:
function safe_source() {
if [ -r $1 ] ; then
source $1
else
logger -t $0 -p crit "unable to source $1"
exit 1
fi
}
safe_source foo
safe_source bar
# ... etc ...
But there is a snag there.
If one of the files foo, bar, etc. have a global such as --
declare GLOBAL_VAR=42
-- it will effectively become:
function safe_source() {
# ...
declare GLOBAL_VAR=42
# ...
}
thus a global variable becomes local.
The question:
An alias in bash seems too weak for this, so must I unroll the above function, and repeat myself, or is there a more elegant approach?
... and yes, I agree that Python, Perl, Ruby would make my life easier, but when working with legacy system, one doesn't always have the privilege of choosing the best tool.
It's a bit late answer, but now declare supports a -g parameter, which makes a variable global (when used inside function). Same works in sourced file.
If you need a global (read exported) variable, use:
declare -g DATA="Hello World, meow!"
Yes, Bash's 'eval' command can make this work. 'eval' isn't very elegant, and it sometimes can be difficult to understand and debug code that uses it. I usually try to avoid it, but Bash often leaves you with no other choice (like the situation that prompted your question). You'll have to weigh the pros and cons of using 'eval' for yourself.
Some background on 'eval'
If you're not familiar with 'eval', it's a Bash built-in command that expects you to pass it a string as its parameter. 'eval' dynamically interprets and executes your string as a command in its own right, in the current shell context and scope. Here's a basic example of a common use (dynamic variable assignment):
$> a_var_name="color"
$> eval ${a_var_name}="blue"
$> echo -e "The color is ${color}."
The color is blue.
See the Advanced Bash Scripting Guide for more info and examples: http://tldp.org/LDP/abs/html/internal.html#EVALREF
Solving your 'source' problem
To make 'eval' handle your sourcing issue, you'd start by rewriting your function, 'safe_source()'. Instead of actually executing the command, 'safe_source()' should just PRINT the command as a string on STDOUT:
function safe_source() { echo eval " \
if [ -r $1 ] ; then \
source $1 ; \
else \
logger -t $0 -p crit \"unable to source $1\" ; \
exit 1 ; \
fi \
"; }
Also, you'll need to change your function invocations, slightly, to actually execute the 'eval' command:
`safe_source foo`
`safe_source bar`
(Those are backticks/backquotes, BTW.)
How it works
In short:
We converted the function into a command-string emitter.
Our new function emits an 'eval' command invocation string.
Our new backticks call the new function in a subshell context, returning the 'eval' command string output by the function back up to the main script.
The main script executes the 'eval' command string, captured by the backticks, in the main script context.
The 'eval' command string re-parses and executes the 'eval' command string in the main script context, running the whole if-then-else block, including (if the file exists) executing the 'source' command.
It's kind of complex. Like I said, 'eval' is not exactly elegant. In particular, there are a couple of special things you should notice about the changes we made:
The entire IF-THEN-ELSE block has becomes one whole double-quoted string, with backslashes at the end of each line "hiding" the newlines.
Some of the shell special characters like '"') have been backslash-escaped, while others ('$') have been left un-escaped.
'echo eval' has been prepended to the whole command string.
Extra semicolons have been appended to all of the lines where a command gets executed to terminate them, a role that the (now-hidden) newlines originally performed.
The function invocation has been wrapped in backticks.
Most of these changes are motived by the fact that 'eval' won't handle newlines. It can only deal with multiple commands if we combine them into a single line delimited by semicolons, instead. The new function's line breaks are purely a formatting convenience for the human eye.
If any of this is unclear, run your script with Bash's '-x' (debug execution) flag turned on, and that should give you a better picture of exactly what's happening. For instance, in the function context, the function actually produces the 'eval' command string by executing this command:
echo eval ' if [ -r <INCL_FILE> ] ; then source <INCL_FILE> ; else logger -t <SCRIPT_NAME> -p crit "unable to source <INCL_FILE>" ; exit 1 ; fi '
Then, in the main context, the main script executes this:
eval if '[' -r <INCL_FILE> ']' ';' then source <INCL_FILE> ';' else logger -t <SCRIPT_NAME> -p crit '"unable' to source '<INCL_FILE>"' ';' exit 1 ';' fi
Finally, again in the main context, the eval command executes these two commands if exists:
'[' -r <INCL_FILE> ']'
source <INCL_FILE>
Good luck.
declare inside a function makes the variable local to that function. export affects the environment of child processes not the current or parent environments.
You can set the values of your variables inside the functions and do the declare -r, declare -i or declare -ri after the fact.

Resources