bash - sanitize the script's parameters containing the '&' sign - linux

I am writing a bash script and trying to do error handling and sanitizing the supplied parameters of the script. The supplied parameters have the form of key/value pairs and are separated by the '&' for the purpose of API compatibility:
cluster=xyz&tenant=abcd1234&key1=value1&key2=value2
In the simplest form, just to print out the supplied parameter, this script is just two lines:
#!/bin/bash
echo "The supplied parameter"
echo "$1"
When calling the script with the parameters in single or double quotes, everything works as expected:
$./script.sh 'cluster=xyz&tenant=abcd1234&key1=value1&key2=value2'
The supplied parameters
cluster=xyz&tenant=abcd1234&key1=value1&key2=value2
$./script.sh "cluster=xyz&tenant=abcd1234&key1=value1&key2=value2"
The supplied parameters
cluster=xyz&tenant=abcd1234&key1=value1&key2=value2
However, if I don't single/double quote the string, it causes the script to hang:
$ ./script.sh cluster=xyz&tenant=abcd1234&key1=value1&key2=value2
[1] 1080
[2] 1081
[3] 1082
[2] Done tenant=abcd1234
[3]+ Done key1=value1
$ The supplied parameters
cluster=xyz
And above stays until I press ctrl+c.
My question - how to properly sanitize the string when it is NOT enclosed in single/double quotes and prevent the above from occurring?
Bash version - GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
Thanks.

Seems there are only two ways to deal with the problem
Option 1 - one parameter as a string, containing '&' signs
One needs to ensure that the supplied parameters string is enclosed in single or double-quotes.
Option 2 - get rid of '&' char entirely and interpret multiple supplied parameters
The script should be called with multiple key/value parameters separated by space:
./script.sh cluster=xyz tenant=abcd1234 key1=value1 key2=value2

Related

How do I pass ">>" or "<<" to my script without the terminal trying to interpret it as me either appending to something or getting stdin?

My python script can take a series of bitwise operators as one of its arguments. They all work fine except for "=<<" which is roll left, and "=>>" which is roll right. I run my script like ./script.py -b +4,-4,=>>10,=<<1, where anything after -b can be any combination of similar operations. As soon as the terminal sees "<<" though, it just drops the cursor to a new line after the command and asks for more input instead of running the script. When it sees ">>", my script doesn't process the arguments correctly. I know it's because bash uses these characters for a specific purpose, but I'd like to get around it while still using "=>>" and "=<<" in my arguments for my script. Is there any way to do it without enclosing the argument in quotation marks?
Thank you for your help.
You should enclose the parameters that contain special symbols into single quotation marks (here, echo represents your script):
> echo '+4,-4,=>>10,=<<1'
+4,-4,=>>10,=<<1
Alternatively, save the parameters to a file (say, params.txt) and read them from the file onto the command line using the backticks:
> echo `cat params.txt`
+4,-4,=>>10,=<<1
Lastly, you can escape some offending symbols:
> echo +4,-4,=\>\>10,=\<\<1
+4,-4,=>>10,=<<1

How to get the complete calling command of a BASH script from inside the script (not just the arguments)

I have a BASH script that has a long set of arguments and two ways of calling it:
my_script --option1 value --option2 value ... etc
or
my_script val1 val2 val3 ..... valn
This script in turn compiles and runs a large FORTRAN code suite that eventually produces a netcdf file as output. I already have all the metadata in the netcdf output global attributes, but it would be really nice to also include the full run command one used to create that experiment. Thus another user who receives the netcdf file could simply reenter the run command to rerun the experiment, without having to piece together all the options.
So that is a long way of saying, in my BASH script, how do I get the last command entered from the parent shell and put it in a variable? i.e. the script is asking "how was I called?"
I could try to piece it together from the option list, but the very long option list and two interface methods would make this long and arduous, and I am sure there is a simple way.
I found this helpful page:
BASH: echoing the last command run
but this only seems to work to get the last command executed within the script itself. The asker also refers to use of history, but the answers seem to imply that the history will only contain the command after the programme has completed.
Many thanks if any of you have any idea.
You can try the following:
myInvocation="$(printf %q "$BASH_SOURCE")$((($#)) && printf ' %q' "$#")"
$BASH_SOURCE refers to the running script (as invoked), and $# is the array of arguments; (($#)) && ensures that the following printf command is only executed if at least 1 argument was passed; printf %q is explained below.
While this won't always be a verbatim copy of your command line, it'll be equivalent - the string you get is reusable as a shell command.
chepner points out in a comment that this approach will only capture what the original arguments were ultimately expanded to:
For instance, if the original command was my_script $USER "$(date +%s)", $myInvocation will not reflect these arguments as-is, but will rather contain what the shell expanded them to; e.g., my_script jdoe 1460644812
chepner also points that out that getting the actual raw command line as received by the parent process will be (next to) impossible. Do tell me if you know of a way.
However, if you're prepared to ask users to do extra work when invoking your script or you can get them to invoke your script through an alias you define - which is obviously tricky - there is a solution; see bottom.
Note that use of printf %q is crucial to preserving the boundaries between arguments - if your original arguments had embedded spaces, something like $0 $* would result in a different command.
printf %q also protects against other shell metacharacters (e.g., |) embedded in arguments.
printf %q quotes the given argument for reuse as a single argument in a shell command, applying the necessary quoting; e.g.:
$ printf %q 'a |b'
a\ \|b
a\ \|b is equivalent to single-quoted string 'a |b' from the shell's perspective, but this example shows how the resulting representation is not necessarily the same as the input representation.
Incidentally, ksh and zsh also support printf %q, and ksh actually outputs 'a |b' in this case.
If you're prepared to modify how your script is invoked, you can pass $BASH_COMMANDas an extra argument: $BASH_COMMAND contains the raw[1]
command line of the currently executing command.
For simplicity of processing inside the script, pass it as the first argument (note that the double quotes are required to preserve the value as a single argument):
my_script "$BASH_COMMAND" --option1 value --option2
Inside your script:
# The *first* argument is what "$BASH_COMMAND" expanded to,
# i.e., the entire (alias-expanded) command line.
myInvocation=$1 # Save the command line in a variable...
shift # ... and remove it from "$#".
# Now process "$#", as you normally would.
Unfortunately, there are only two options when it comes to ensuring that your script is invoked this way, and they're both suboptimal:
The end user has to invoke the script this way - which is obviously tricky and fragile (you could however, check in your script whether the first argument contains the script name and error out, if not).
Alternatively, provide an alias that wraps the passing of $BASH_COMMAND as follows:
alias my_script='/path/to/my_script "$BASH_COMMAND"'
The tricky part is that this alias must be defined in all end users' shell initialization files to ensure that it's available.
Also, inside your script, you'd have to do extra work to re-transform the alias-expanded version of the command line into its aliased form:
# The *first* argument is what "$BASH_COMMAND" expanded to,
# i.e., the entire (alias-expanded) command line.
# Here we also re-transform the alias-expanded command line to
# its original aliased form, by replacing everything up to and including
# "$BASH_COMMMAND" with the alias name.
myInvocation=$(sed 's/^.* "\$BASH_COMMAND"/my_script/' <<<"$1")
shift # Remove the first argument from "$#".
# Now process "$#", as you normally would.
Sadly, wrapping the invocation via a script or function is not an option, because the $BASH_COMMAND truly only ever reports the current command's command line, which in the case of a script or function wrapper would be the line inside that wrapper.
[1] The only thing that gets expanded are aliases, so if you invoked your script via an alias, you'll still see the underlying script in $BASH_COMMAND, but that's generally desirable, given that aliases are user-specific.
All other arguments and even input/output redirections, including process substitutiions <(...) are reflected as-is.
"$0" contains the script's name, "$#" contains the parameters.
Do you mean something like echo $0 $*?

Multiword string as a curl option using Bash

I want to get some data from a HTTP server. What it sends me depends on what I put in a POST request.
What I put in the INPUT_TEXT field is a sequence of words. When I run the following command, I get good looking output.
$ curl http://localhost:59125/process -d INPUT_TEXT="here are some words"
I want a bash script to take some string as a command line argument, and pass it appropriately to curl. The first thing I tried was to put the following in a script:
sentence=$1
command="curl http://localhost:59125/process -d INPUT_TEXT=\"${sentence}\""
$command
I then run the script like so:
$ ./script "here are some words"
But then I get a curl Couldn't resolve host error for each of "are", "some", and "words". It would seem that "here" got correctly treated as the INPUT_TEXT, but the rest of the words were then considered to be hosts, and not part of the option.
So I tried:
command=("curl" "http://localhost:59125/process" "-d" "INPUT_TEXT='$sentence'")
${command[#]}
I got the same output as the first script. I finally got what I wanted with:
result=$(curl http://localhost:59125/process -d INPUT_TEXT="${sentence}")
echo $result
I'm still unsure as to what the distinction is. In the first two cases, when I echoed out the contents of command, I get exactly what I input from the interactive Bash prompt, which had worked fine. What caused the difference?
The following will work:
command=("curl" "http://localhost:59125/process"
"-d" "INPUT_TEXT=$sentence")
"${command[#]}"
That has two changes from yours:
I removed the incorrect quotes around $sentence since you don't want to send quotes to the server (as far as I can see).
I put double-quotes around the use of "${command[#]}". Without the double quotes, the array's elements are concatenated with spaces between them and then the result is word-split. With double quotes, the individual array elements are used as individual words.
The second point is well-explained in the bash FAQ and a bunch of SO answers dealing with quotes.
The important thing to understand is that quotes only quote when a command is parsed. A quote which is a character in a variable is just a character; it is not reinterpreted when the value of the variable expanded. Whitespace in the variable is used for word-splitting if the variable expansion is unquoted; the fact that the whitespace was quoted in the the command which defined the variable is completely irrelevant. In this sense, bash is just the same as any other programming language.

Why doesnt command execute successfully if i use variables?

I have the following script
WSO2_SCRIPT="JAVA_HOME=$JAVA_HOME /opt/autopilot/wso2is/bin/wso2server.sh"
WSO2_LOG="/var/log/autopilot/wso2is/autopilot-wso2is-initd.log"
${WSO2_SCRIPT} start >> ${WSO2_LOG} 2>&1 || echo failed
JAVA_HOME=$JAVA_HOME /opt/autopilot/wso2is/bin/wso2server.sh start >> /var/log/autopilot/wso2is/autopilot-wso2is-initd.log 2>&1 || echo failedagain
The third line of the code results in failure as I have "failed" echoed?
However the fourth line is successful and I don't get "failedagain" echoed.
Line 3 and 4 should result in exactly the same thing. Only difference is I am using variables in in line 3, and being explicit in line 4.
Why does using variables result in a failure?
When variables are expanded without quotes, they undergo word splitting and pathname expansion, but not shell grammar parsing.
This means that you can put the following in variables:
Multiple arguments (including command name) to be split up on spaces and spaces only
Globs like *.txt to be expanded
It also means that you can not put anything of the following in variables:
Redirections
Quotes
Pipes
Backgrounding &
Conditionals and loops, including if and [[ .. ]]
Brace and parenthesis groups
Parameter expansion
Command substitutions
Process substitutions
and as you've discovered: variable assignments
If you want to pass any of the above around as a variable, you should use a function and refer to the function instead. If you don't care about security and good practice, you can also use eval to evaluate a text string as shell code.

Semicolon on command line in linux

i am running my application in linux by providing inputs as command line. My input field contain an argument which contains ";"(semicolon) internally.(For example:123;434;5464).
This will be parsed using UTF8String encode and send.
But when i am using like this, in initial itself i am getting,
bash: 434: command not found
bash: 5464: command not found
And when i capture traffic the output contains only 123 instead 123;434;5464
But if i give without semicolon (Ex:123:434:5464),not getting any problem output coming properly as 123:434:5464
Point me how to give command line input by using semicolon as to come output. Is there any particular syntax to use while doing with semicolon.
I am running like below
./runASR.sh -ip 10.78.242.4 -port 3868 -sce 10.78.241.206 -id 85;167838865;1385433280
where -id field contain that value with issue.
; is treated an end of command character. So 123;456;5464 to bash is in fact 3 commands. To pass such meta-characters escape it with escape character \.
./command 123\;456\;5464
Or Just quote it with single quote (double quote evaluates the inner string) (Thanks Triplee, I forgot to mention this)
./command '123;456;5464'

Resources