strange behaviour of a subshell executing command with arguments - bash

when I execute the following command directly:
root#busybox-694d76bb5d-2gcvh:/# mysql -hmariadb -P3306 -uroot -ppassword -e 'SELECT 1'
I get the following output:
mysql: [Warning] Using a password on the command line interface can be insecure.
+---+
| 1 |
+---+
| 1 |
+---+
However, the same command in a subshell, like so:
$(mysql -hmariadb -P3306 -uroot -ppassword -e 'SELECT 1')
outputs:
mysql: [Warning] Using a password on the command line interface can be insecure.
bash: 1: command not found
why does bash interprets the "1" I send as an argument as if it was a separate command when it is sent in a subshell?

It's not the 1 that's part of the arguments that is interpreted by bash, it's the output of the command.
First, you need to know that mysql has two default modes of output : table and raw. The first is used when the command's output stream is a terminal and prints an human-readable output. The second is used otherwise and prints an output easily manipulated by scripts. You can force one or the other by using the options whose documentation I've linked.
Secondly, you need to know the difference between a simple (subshell) and $(command substitution) : in both cases the command is run in a subshell, but with command substitution the output of the command is substituted in the original command line which is then executed.
So what happened when you wrote your $(mysql command) is that its output was 1 (the raw result) instead of the table you previously saw, which then failed to be parsed as a command.
You can see the difference between subshell and command substitution more easily by testing the difference between (echo 1) and $(echo 1), the second of which will fail in the exact same way as your current command.

Related

why does "echo "qwerty" | /bin/sh" return "/bin/sh: 1: qwerty: not found"?

I know that echo command displays the line of text that is passed as argument.
So the syntax echo "qwerty" would display:
qwerty
but when I merge the previous syntax with | /bin/sh the following message is displayed:
/bin/sh: 1: qwerty: not found
I would like to know why using bitwise OR operator (i.e. | ) this way ending up with such an output.
| is not a bitwise OR operator.[1] It's a pipe operator. It causes the stdout of the preceding program to be piped to the stdin of the following program.
$ printf 'abc def\nghi\n' | wc
2 3 12
This shows wc ("word count") reading the output of printf and printing out the fact that it received 2 lines, 3 words and 12 bytes.
In your case, sh reads its stdin for commands (due to the absence of both a -c option and a file name argument), and thus treats qwerty as a command to execute.
It can be bitwise OR in arithmetic context when using bash and possibly other shells in the "sh family". That's not the case here even if you were using bash.

Bash verbose command with evaluating parameters

I am trying to log the command with its parameters (after evaluating if necessary) in Bash.
I am trying to use set -v:
$ variable=2
$ set -v
$ sleep $variable
sleep $variable
As you can see, it prints sleep $variable. I want to log sleep 2 instead.
My original command is more complex, so I don't want to echo each parameter one by one. (And it is probably more error prone to do so).
set -v (or set -o verbose) will output every command as it is read, without expanding things.
set -x (or set -o xtrace) will output the expanded command line before execution. Each line is prepended by the PS4 prompt (usually a +) and for commands executed as part of command substitutions, the prompt will be "doubled up" (++).
The trace will be written to the file descriptor indicated by $BASH_XTRACEFD (or to the shell's standard error by default).

Why doesn't LIMIT=\`ulimit -u\` work in bash?

In my program I need to know the maximum number of process I can run. So I write a script. It works when I run it in shell but but when in program using system("./limit.sh"). I work in bash.
Here is my code:
#/bin/bash
LIMIT=\`ulimit -u\`
ACTIVE=\`ps -u | wc -l \`
echo $LIMIT > limit.txt
echo $ACTIVE >> limit.txt
Anyone can help?
Why The Original Fails
Command substitution syntax doesn't work if escaped. When you run:
LIMIT=\`ulimit -u\`
...what you're doing is running a command named
-u`
...with the environment variable named LIMIT containing the value
`ulimit
...and unless you actually have a command that starts with -u and contains a backtick in its name, this can be expected to fail.
This is because using backticks makes characters which would otherwise be syntax into literals, and running a command with one or more var=value pairs preceding it treats those pairs as variables to export in the environment for the duration of that single command.
Doing It Better
#!/bin/bash
limit=$(ulimit -u)
active=$(ps -u | wc -l)
printf '%s\n' "$limit" "$active" >limit.txt
Leave off the backticks.
Use modern $() command substitution syntax.
Avoid multiple redirections.
Avoid all-caps names for your own variables (these names are used for variables with meaning to the OS or system; lowercase names are reserved for application use).
Doing It Right
#!/bin/bash
exec >limit.txt # open limit.txt as output for the rest of the script
ulimit -u # run ulimit -u, inheriting that FD for output
ps -u | wc -l # run your pipeline, likewise with output to the existing FD
You have a typo on the very first line: #/bin/bash should be #!/bin/bash - this is often known as a "shebang" line, for "hash" (#) + "bang" (!)
Without that syntax written correctly, the script is run through the system's default shell, which will see that line as just a comment.
As pointed out in comments, that also means only the standardised options available to the builtin ulimit command, which doesn't include -u.

How to escape commands in Makefile for killing process remotely?

server_stop:
ssh $(SERVER_USERNAME)#$(SERVER_HOSTNAME) \
"kill $$(ps aux | grep '[p]ython abc-server' | awk '{print $$2}')"
This gives
bash: line 0: kill: (60403) - No such process
bash: line 1: 60364: command not found
I believe the brackets around p are not escaped correctly. How do I do this?
If you know the command line, you don't need to use ps + grep. Use instead, pgrep.
server_stop:
ssh $(SERVER_USERNAME)#$(SERVER_HOSTNAME) \
'kill $$(pgrep -f "[p]ython abc-server")'
The -f allows you to pass the full command line to be found.
In order to avoid the shell evaluation to the command $$(pgrep -f "[p]ython abc-server"), surround it with single quotes, so the evaluation will happen in the target server.
Note: If possible, keep a start/stop script inside your server, so your ssh command will only call the script, avoiding the current issue.

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!

Resources