Construct a variable with command parameters that contains a space - bash

I have the following in a bash script
COUNTRY="$(findcountry /data/list.tab)"
PARAMS="--name=Test"
PARAMS="$PARAMS --country=$COUNTRY"
./program $PARAMS
If $(findcountry /data/list.tab) returns e.g. Mexico, there is no issue.
But if it returns United States, there's an issue because of the space.
In that case calls the "program" with 3 arguments, because one of the arguments contains a space. like it was called like so:
./program '--name=Test' '--country=United' 'States'
The program does not understand this, it expects there to be only 2 arguments, like.
./program '--name=Test' '--country=United States'
How can I fix this ?
(Note that the 4 lines of bash script is just a vast simplification, there are a lot more arguments than the 2 shown here that I add to my $PARAMS , all which could also have the issue of containing whitespace..)

Use an array; it's what they're made for.
COUNTRY="$(findcountry /data/list.tab)"
PARAMS=( "--name=Test" )
PARAMS+=("--country=$COUNTRY")
./program "${PARAMS[#]}"

When a string is used unquoted, it is split on the values of IFS.
That is something you want to use to separate both arguments.
A very simple solution that works for two arguments is:
country="$(findcountry /data/list.tab)"
params="--name=Test"
./program "$params" "--country=$country"
For more arguments, you should use an array variable and +=:
country="$(findcountry /data/list.tab)"
params+=("--country=$country")
params+=("--name=Test")
./program "${params[#]}"

Related

Use default value if no arguments are set except the first one in a bash script

From "Process all arguments except the first one (in a bash script)" I have learned how to get all arguments except the first one. Is it also possible to substitute null with another value, so I can define a default value?
I've tried the following, but I think I don't get some little detail about the syntax:
DEFAULTPARAM="up"
echo "${#:2-$DEFAULTPARAM}"
Here are my test cases:
$ script.sh firstparam another more
another more
$ script.sh firstparam another
another
$ script.sh firstparam
up
You cannot combine 2 expressions like that in bash. You can get all arguments from position 2 into a separate variable and then check/get default value:
defaultParam="up"
from2nd="${#:2}" # all arguments from position 2
echo "${from2nd:-$defaultParam}" # return default value if from2nd is empty
PS: It is recommended to avoid all caps variable names in your bash script.
Testing:
./script.sh firstparam
up
./script.sh firstparam second
second
./script.sh firstparam second third
second third
bash doesn't generally allow combining different types of special parameter expansions; this is one of the combos that doesn't work. I think the easiest way to get this effect is to do an explicit test to decide whether to use the default value. Exactly how to do this depends on the larger situation, but probably something like this:
#!/bin/bash
DEFAULTPARAM="up"
if (( $# >= 2 )); then
allbutfirstarg=("${#:2}")
else
allbutfirstarg=("$DEFAULTPARAM")
fi
echo "${allbutfirstarg[#]}"

Append bash parameters and pass forward to other script

I need to pass further original parameters and also I want to add some others. Something like this:
#!/bin/bash
params="-D FOREGROUND"
params+=" -c Include conf/dev.conf"
/usr/local/apache/bin/apachectl $params "$#"
This code above don't work as expected if params contains of two or more parameters, it treated as one parameter.
The code in your example should work if the following command is valid when executed at the command line written exactly like this :
/usr/local/apache/bin/apachectl -D FOREGROUND -c Include conf/dev.conf "$#"
A quick web search leads me to think that what you want is this (notice the additional double quotes) :
/usr/local/apache/bin/apachectl -D FOREGROUND -c "Include conf/dev.conf" "$#"
Here is how to achieve that simply and reliably with arrays, in a way that sidesteps "quoting inside quotes" issues:
#!/bin/bash
declare -a params=()
params+=(-D FOREGROUND)
params+=(-c "Include conf/dev.conf")
/usr/local/apache/bin/apachectl "${params[#]}" "$#"
The params array contains 4 strings ("-D", "FOREGROUND", "-c" and "Include conf/dev/conf"). The array expansion ("${params[#]}", note that the double quotes are important here) expands them to these 4 strings, as if you had written them with double quotes around them (i.e. without further word splitting).
Using arrays with this kind of expansion is a flexible and reliable to way to build commands and then execute them with a simple expansion.
If the issue is the space in the parameter "-c Include conf/dev.conf" then you could just use a backspace to preserve the space character:
params+="-c Include\ conf/dev.conf"

error in shell script: unexpected end of file

The following script is showing me "unexpected end of file" error. I have no clue why am I facing this error. My all the quotes are closed properly.
#!/usr/bin/sh
insertsql(){
#sqlite3 /mnt/rd/stats_flow_db.sqlite <<EOF
echo "insert into flow values($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18)"
#.quit
}
for i in {1..100}
do
src_ip = "10.1.2."+$i
echo $src_ip
src_ip_octets = ${src_ip//,/}
src_ip_int = $src_ip_octets[0]*1<<24+$src_ip_octets[1]*1<<16+$src_ip_octets[2]*1<<8+$src_ip_octets[3]
dst_ip = "10.1.1."+$i
dst_ip_octets = ${dst_ip//,/}
dst_ip_int = $dst_ip_octets[0]*1<<24+$dst_ip_octets[1]*1<<16+$dst_ip_octets[2]*1<<8+$dst_ip_octets[3]
insertsql(1, 10000, $dst_ip, 20000, $src_ip, "2012-08-02,12:30:25.0","2012-08-02,12:45:25.0",0,0,0,"flow_a010105_a010104_47173_5005_1_50183d19.rrd",0,12,$src_ip_int,$dst_ip_int,3,50000000,80000000)
done
That error is caused by <<. When encountering that, the script tries to read until it finds a line which has exactly (starting in the first column) what is found after the <<. As that is never found, the script searches to the end and then complains that the file ended unexpectedly.
That will not be your only problem, however. I see at least the following other problems:
You can only use $1 to $9 for positional parameters. If you want to go beyond that, the use of the shift command is required or, if your version of the shell supports it, use braces around the variable name; e.g. ${10}, ${11}...
Variable assignments must not have whitespace arount the equal sign
To call your insertsql you must not use ( and ); you'd define a new function that way.
The cass to your insertsql function must pass the parameters whitespace separated, not comma separated.
A couple of problems:
There should be no space between equal sign and two sides of an assignment: e.g.,: dst_ip="10.1.1.$i"
String concatenation is not done using plus sign e.g., dst_ip="10.1.1.$i"
There is no shift operator in bash, no <<: $dst_ip_octets[0]*1<<24 can be done with expr $dst_ip_octets[0] * 16777216 `
Functions are called just like shell scripts, arguments are separated by space and no parenthesis: insertsql 1 10000 ...
That is because you don't follow shell syntax.
To ser variable you are not allowed to use space around = and to concatenate two parts of string you shouldn't use +. So the string
src_ip = "10.1.2."+$i
become
src_ip="10.1.2.$i"
Why you're using the string
src_ip_octets = ${src_ip//,/}
I don't know. There is absolutely no commas in you variable. So even to delete all commas it should look like (the last / is not required in case you're just deleting symbols):
src_ip_octets=${src_ip//,}
The next string has a lot of symbols that shell intepreter at its own way and that's why you get the error about unexpected end of file (especially due to heredoc <<)
src_ip_int = $src_ip_octets[0]*1<<24+$src_ip_octets[1]*1<<16+$src_ip_octets[2]*1<<8+$src_ip_octets[3]
So I don't know what exactly did you mean, though it seems to me it should be something like
src_ip_int=$(( ${src_ip_octets%%*.}+$(echo $src_ip_octets|sed 's/[0-9]\+\.\(\[0-9]\+\)\..*/\1/')+$(echo $src_ip_octets|sed 's/\([0-9]\+\.\)\{2\}\(\[0-9]\+\)\..*/\1/')+${src_ip_octets##*.} ))
The same stuff is with the next strings.
You can't do this:
dst_ip_int = $dst_ip_octets[0]*1<<24+$dst_ip_octets[1]*1<<16+$dst_ip_octets[2]*1<<8+$dst_ip_octets[3]
The shell doesn't do math. This isn't C. If you want to do this sort of calculation, you'll need to use something like bc, dc or some other tool that can do the sort of math you're attempting here.
Most of those operators are actually shell metacharacters that mean something entirely different. For example, << is input redirection, and [ and ] are used for filename globbing.

Variables from stdin

I want to get an input from the user using read
read line
and the proper input would be a string and then a number:
a 5
b 6
+ 87
How do you separate the "a" and 5 into two variables, with the 5 into a integer variable?
read supports the command line option -a, to store the input in an array like that:
$ read -a line
a 4
$ echo ${line[0]}
a
$ echo ${line[1]}
4
That would be nicer than using two variables, in my opinion.
I suggest reading the documenentation of read to get you started:
help read
Here are the first two paragraphs:
Read a line from the standard input and split it into fields.
Reads a single line from the standard input, or from file descriptor FD
if the -u option is supplied. The line is split into fields as with word
splitting, and the first word is assigned to the first NAME, the second
word to the second NAME, and so on, with any leftover words assigned to
the last NAME. Only the characters found in $IFS are recognized as word
delimiters.
Note that bash doesn't have a notion of "integer variables" comparable to other programming languages. Bash variables are untyped. Declaring a variable as integer using declare -i only influences assignments to this variable -- everything that is not a valid integer is silently set to 0.
I presume you're operating in the shell since this post is tagged "bash" but you might want to make that explicit.
Anyway, the "read" command to the shell takes multiple variable names, not just one. You can give it two and it will hand each word on the line to you in the respective variables. (They're split on the field separator given by the IFS variable.)
The shell doesn't really have any distinction between "integer" variables and any other kind in the general case.
I suggest reading the man page for the shell fully if you really want to understand how to write shell scripts properly.
The read command will split your input along whatever is in $IFS. This, by default is whitespace, so simply doing this:
read my_string my_number
will split your input into two sections separated by the space. Sometimes, you'll see this:
read my_string my_number garbage
Because read will read in the entire rest of the line into the last variable no matter how many parameters you had. For example, if I had:
read my_string my_number
And the user put in:
this 1 foo foo foo!
$my_string will be this, but $my_number will be 1 foo foo foo!
By putting another variable (called garbage in this case), I eliminate this issue. If I put:
read my_string my_number garbage
And the user put in:
this 1 foo foo foo!
$my_string will be this, $my_number will be 1, and $garbage would be foo foo foo!.
A simple test program:
while read my_string my_number garbage
do
echo "The string is '$my_string'. The number is '$my_number'."
echo "Note there's no error checking of input."
echo "That's your job, Bunky."
done

bash command expansion

The following bash command substitution does not work as I thought.
echo $TMUX_$(echo 1)
only prints 1 and I am expecting the value of the variable $TMUX_1.I also tried:
echo ${TMUX_$(echo 1)}
-bash: ${TMUXPWD_$(echo 1)}: bad substitution
Any suggestions ?
If I understand correctly what you're looking for, you're trying to programatically construct a variable name and then access the value of that variable. Doing this sort of thing normally requires an eval statement:
eval "echo \$TMUX_$(echo 1)"
Important features of this statement include the use of double-quotes, so that the $( ) gets properly interpreted as a command substitution, and the escaping of the first $ so that it doesn't get evaluated the first time through. Another way to achieve the same thing is
eval 'echo $TMUX_'"$(echo 1)"
where in this case I used two strings which automatically get concatenated. The first is single-quoted so that it's not evaluated at first.
There is one exception to the eval requirement: Bash has a method of indirect referencing, ${!name}, for when you want to use the contents of a variable as a variable name. You could use this as follows:
tmux_var = "TMUX_$(echo 1)"
echo ${!tmux_var}
I'm not sure if there's a way to do it in one statement, though, since you have to have a named variable for this to work.
P.S. I'm assuming that echo 1 is just a stand-in for some more complicated command ;-)
Are you looking for arrays? Bash has them. There are a number of ways to create and use arrays in bash, the section of the bash manpage on arrays is highly recommended. Here is a sample of code:
TMUX=( "zero", "one", "two" )
echo ${TMUX[2]}
The result in this case is, of course, two.
Here are a few short lines from the bash manpage:
Bash provides one-dimensional indexed and associative array variables. Any variable may be
used as an indexed array; the declare builtin will explicitly declare an array. There is
no maximum limit on the size of an array, nor any requirement that members be indexed or
assigned contiguously. Indexed arrays are referenced using integers (including arithmetic
expressions) and are zero-based; associative arrays are referenced using arbitrary
strings.
An indexed array is created automatically if any variable is assigned to using the syntax
name[subscript]=value. The subscript is treated as an arithmetic expression that must
evaluate to a number greater than or equal to zero. To explicitly declare an indexed
array, use declare -a name (see SHELL BUILTIN COMMANDS below). declare -a name[subscript]
is also accepted; the subscript is ignored.
This works (tested):
eval echo \$TMUX_`echo 1`
Probably not very clear though. Pretty sure any solutions will require backticks around the echo to get that to work.

Resources