Checking the number of param in bash shell script - linux

I use for checking the number of params in bash shell as follows:
#! /bin/bash
usage() {
echo "Usage: $0 <you need to specify at least 1 param>"
exit -1
}
[ x = x$1 ] && usage
where, if [ x = x$1 ] condition is not satisfied, execute usage.
Here, my question is, I never really think about the expression [ x = x$1 ] which looks a lot like a condition expression. Is x counted as a literal? and how come can we use = for comparison. Typically should it be something like ==?
Could anybody please fill the void here?

[ x = y ] condition is for comparing strings. (just once =)
x$1 means concatenating two string x and $1.
So, if $1 is empty, x$1 equals x, and [ x = x ] will be true as a result.

[ x = x$1 ] is error prone, don't use it. Do this instead [ "$1" ]
The difference is that if $1 contains a space or other special characters, your script will crash, for example:
$ a='hello world'
$ [ x = x$a ] && echo works
-bash: [: too many arguments
To fix this you could do [ x = x"$1" ], but [ "$1" ] is shorter, so what's the point.
[] expressions are used extensively in shell scripts, I recommend to read help test. The [ is a synonym for the "test" builtin, but the last argument must be a literal ], to match the opening [. In there you will find the explanation of the differences between = and == operators.
Finally, literal text in conditions is evaluated to true if not empty. Some more examples:
[ x ] # true
[ abc ] # true
[ a = a ] # true
[ a = x ] # false
[ '' ] # false
[ b = '' ] # false
Also common gotchas are these:
[ 0 ] # true
[ -n ] # true
[ -blah ] # true
[ false ] # true
These are true, because 0, -n, false or anything being the only argument, they are treated as literal strings, and so the condition evaluates to true.

Use $# to count the number of parameters and use the OR operator instead of the AND so that usage() only gets executed if the first condition fails.
#! /bin/bash
usage() {
echo "Usage: $0 <you need to specify at least 1 param>"
exit -1
}
[ x = $# ] || usage

Related

Ignore the 1st row(header) in a csv files using shell script

Following is my csv file
Contact Id,Customer Code,Billing Account Code,Bank BSB,Bank ID
2222222220,2222222222222220,100,084004,fjksanfjkdsksdnfnkjsnQ==
3333333330,3333333333333330,100,084789,sklsnfksnkdfnkgndfkjgn==
and this is the code I'm using to ignore header & any row which has no data in any of 5 columns
while IFS=',' read cont_id cust_code bac bsb bid
do
if [ "$cont_id" == "" ] || [ "$cust_code" == "" ] || [ "$bac" == "" ] || [ "$bsb" == "" ] || [ "$bid" == "" ]; then
echo $cont_id,$cust_code,$bac,$bsb,$bid >> $SOURCE_DIR/dummyRejectedRecords.csv
elif [ "$cont_id" == "Customer Code" ] && [ "$cust_code" == "Customer" ] && [ "$bac" == "Billing Account Code" ] && [ "$bsb" == "Bank BSB" ] &&[ "$bid" == "Bank ID" ]; then
echo $cont_id,$cust_code,$bac,$bsb,$bid >> $SOURCE_DIR/dummyRejectedRecords.csv
else
echo "Contact_Id = '"$cont_id"'"
echo "Customer_Code = '"$cust_code"'"
echo "Billing_Account_Code = '"$bac"'"
echo "Bank_ID = '"$bsb"'"
echo "Bank_BSB = '"$bid"'"
echo ""
#ADD YOUR PROCEDURE HERE
fi
done < $SOURCE_DIR/dummy.csv
The problem is that 1st row is not being ignored
and this is being appended to 1x1 value of csv Customer Code = 'Customer Code'
even if header is ignored the 1st value of next row is appended with 
Could someone help me here (without using awk command)
Thanks a ton in advance
The first thing that comes to mind is to use a simple control variable to skip the first iteration, provided that the file always contains a header that has to be ignored as the first line.
Add this before the first if statement inside the loop:
if [ -z "$HEADER_DONE" ]; then
HEADER_DONE=1
continue
fi

What does the shell "${i%,v}" mean in run-parts.sh?

Currently, I am studying crond on Centos7 and want to take a close look at run-parts.sh. But I find some strange scripts such as:
"${i%,v}"
What does it mean? I know ${i%%.\*}, ${i##.*}, ${i,}, and ${i,,}.
Here is the part script, thanks every one in advance.
for i in $(LC_ALL=C;echo ${1%/}/*[^~,]) ; do
[ -d $i ] && continue
# Don't run *.{rpmsave,rpmorig,rpmnew,swp,cfsaved} scripts
[ "${i%.cfsaved}" != "${i}" ] && continue
[ "${i%.rpmsave}" != "${i}" ] && continue
[ "${i%.rpmorig}" != "${i}" ] && continue
[ "${i%.rpmnew}" != "${i}" ] && continue
[ "${i%.swp}" != "${i}" ] && continue
[ "${i%,v}" != "${i}" ] && continue
The syntax ${var%pattern} just expands to the value of $var with the glob pattern matcing 'pattern' removed from the end of the string. So ${i%,v} is just $i with the trailing ,v removed. The only difference between ${i%%,v} and ${i%,v} is that the former will match the longest possible match, but that's irrelevant here since ,v can only expand to the literal string ,v. It's stated best in the documentation (http://pubs.opengroup.org/onlinepubs/9699919799/):
${parameter%[word]}
Remove Smallest Suffix Pattern. The word shall be expanded to produce apattern. The parameter expansion shall then result in parameter, with the smallest portion of the suffix matched by the pattern deleted. If present, word shall not begin with an unquoted '%'.
${parameter%%[word]}
Remove Largest Suffix Pattern. The word shall be expanded to produce a pattern. The parameter expansion shall then result in parameter, with the largest portion of the suffix matched by the pattern deleted

Having trouble with simple Bash if/elif/else statement

I'm writing bash scripts that need to work both on Linux and on Mac.
I'm writing a function that will return a directory path depending on which environment I'm in.
Here is the pseudo code:
If I'm on a Mac OS X machine, I need my function to return the path:
/usr/local/share/
Else if I'm on a Linux machine, I need my function to return the path:
/home/share/
Else, you are neither on a Linux or a Mac...sorry.
I'm very new to Bash, so I apologize in advance for the really simple question.
Below is the function I have written. Whether I'm on a Mac or Linux, it always returns
/usr/local/share/
Please take a look and enlighten me with the subtleties of Bash.
function get_path(){
os_type=`uname`
if [ $os_type=="Darwin" ]; then
path="/usr/local/share/"
elif [ $os_type=="Linux" ]; then
path="/home/share/"
else
echo "${os_type} is not supported"
exit 1
fi
echo $path
}
You need spaces around the operator in a test command: [ $os_type == "Darwin" ] instead of [ $os_type=="Darwin" ]. Actually, you should also use = instead of == (the double-equal is a bashism, and will not work in all shells). Also, the function keyword is also nonstandard, you should leave it off. Also, you should double-quote variable references (like "$os_type") just in case they contain spaces or any other funny characters. Finally, echoing an error message ("...not supported") to standard output may confuse whatever's calling the function, because it'll appear where it expected to find a path; redirect it to standard error (>&2) instead. Here's what I get with these cleaned up:
get_path(){
os_type=`uname`
if [ "$os_type" = "Darwin" ]; then
path="/usr/local/share/"
elif [ "$os_type" = "Linux" ]; then
path="/home/share/"
else
echo "${os_type} is not supported" >&2
exit 1
fi
echo "$path"
}
EDIT: My explanation of the difference between assignments and comparisons got too long for a comment, so I'm adding it here. In many languages, there's a standard expression syntax that'll be the same when it's used independently vs. in test. For example, in C a = b does the same thing whether it's alone on a line, or in a context like if ( a = b ). The shell isn't like that -- its syntax and semantics vary wildly depending on the exact context, and it's the context (not the number of equal signs) that determines the meaning. Here are some examples:
a=b by itself is an assignment
a = b by itself will run a as a command, and pass it the arguments "=" and "b".
[ a = b ] runs the [ command (which is a synonym for the test command) with the arguments "a", "=", "b", and "]" -- it ignores the "]", and parses the others as a comparison expression.
[ a=b ] also runs the [ (test) command, but this time after removing the "]" it only sees a single argument, "a=b" -- and when test is given a single argument it returns true if the argument isn't blank, which this one isn't.
bash's builtin version of [ (test) accepts == as a synonym for =, but not all other versions do.
BTW, just to make things more complicated bash also has [[ ]] expressions (like test, but cleaner and more powerful) and (( )) expressions (which are totally different from everything else), and even ( ) (which runs its contents as a command, but in a subshell).
You need to understand what [ means. Originally, this was a synonym for the /bin/test command. These are identical:
if test -z "$foo"
then
echo "String '$foo' is null."
fi
if [ -z "$foo" ]
then
echo "String '$foo' is null."
fi
Now, you can see why spaces are needed for all of the parameters. These are parameters and not merely boolean expressions. In fact, the test manpage is a great place to learn about the various tests. (Note: The test and [ are built in commands to the BASH shell.)
if [ $os_type=="Darwin" ]
then
This should be three parameters:
"$os_type"
= and not ==
"Darwin"
if [ "$os_type" = "Darwin" ] # Three parameters to the [ command
then
If you use single square brackets, you should be in the habit to surround your parameters with quotation marks. Otherwise, you will run into trouble:
foo="The value of FOO"
bar="The value of BAR"
if [ $foo != $bar ] #This won't work
then
...
In the above, the shell will interpolate $foo and $bar with their values before evaluating the expressions. You'll get:
if [ The value of FOO != The value of BAR ]
The [ will look at this and realize that neither The or value are correct parameters, and will complain. Using quotes will prevent this:
if [ "$foo" != "$bar" ] #This will work
then
This becomes:
if [ "The value of FOO" != "The value of BAR" ]
This is why it's highly recommended that you use double square brackets for your tests: [[ ... ]]. The test looks at the parameters before the shell interpolates them:
if [[ $foo = $bar ]] #This will work even without quotation marks
Also, the [[ ... ]] allows for pattern matching:
if [[ $os_type = D* ]] # Single equals is supported
then
path="/usr/local/share/"
elif [[ $os_type == L* ]] # Double equals is also supported
then
path="/home/share/"
else
echo "${os_type} is not supported"
exit 1
fi
This way, if the string is Darwin32 or Darwin64, the if statement still functions. Again, notice that there has to be white spaces around everything because these are parameters to a command (actually, not anymore, but that's the way the shell parses them).
Adding spaces between the arguments for the conditionals fixed the problem.
This works
function get_path(){
os_type=`uname`
if [ $os_type == "Darwin" ]; then
path="/usr/local/share/"
elif [ $os_type == "Linux" ]; then
path="/home/share/"
else
echo "${os_type} is not supported"
exit 1
fi
echo $path
}

If...else if...else in REBOL

I've noticed that REBOL doesn't have a built in if...elsif...else syntax, like this one:
theVar: 60
{This won't work}
if theVar > 60 [
print "Greater than 60!"
]
elsif theVar == 3 [
print "It's 3!"
]
elsif theVar < 3 [
print "It's less than 3!"
]
else [
print "It's something else!"
]
I have found a workaround, but it's extremely verbose:
theVar: 60
either theVar > 60 [
print "Greater than 60!"
][
either theVar == 3 [
print "It's 3!"
][
either theVar < 3 [
print "It's less than 3!"
][
print "It's something else!"
]
]
]
Is there a more concise way to implement an if...else if...else chain in REBOL?
The construct you would be looking for would be CASE. It takes a series of conditions and code blocks to evaluate, evaluating the blocks only if the condition is true and stopping after the first true condition is met.
theVar: 60
case [
theVar > 60 [
print "Greater than 60!"
]
theVar == 3 [
print "It's 3!"
]
theVar < 3 [
print "It's less than 3!"
]
true [
print "It's something else!"
]
]
As you see, getting a default is as simple as tacking on a TRUE condition.
Also: if you wish, you can have all of the cases run and not short circuit with CASE/ALL. That prevents case from stopping at the first true condition; it will run them all in sequence, evaluating any blocks for any true conditions.
And a further option is to use all
all [
expression1
expression2
expression3
]
and as long as each expression returns a true value, they will continue to be evaluated.
so,
if all [ .. ][
... do this if all of the above evaluate to true.
... even if not all true, we got some work done :)
]
and we also have any
if any [
expression1
expression2
expression3
][ this evaluates if any of the expressions is true ]
You can use the case construct for this, or the switch construct.
case [
condition1 [ .. ]
condition2 [ ... ]
true [ catches everything , and is optional ]
]
The case construct is used if you're testing for different conditions. If you're looking at a particular value, you can use switch
switch val [
va1 [ .. ]
val2 [ .. ]
val3 val4 [ either or matching ]
]

Shell script showing errors while executing in Linux

SCRIPT :
IMAGE=$imgvalue;
if [ $imgvalue :=1 ]
then
echo DO=ABC;
elif [ $imgvalue :=2 ]
then
echo DO=ETC;
elif [ $imgvalue :=3 ]
then
echo DO=XYZ;
else
echo "$imgvalue is unsupported";
exit 1;
fi
In the script above, IMAGE=1, IMAGE=2, IMAGE=3 whatever may be the value I have assigned. It's showing only DO=ABC. Other conditions not working. Can anyone explain what's wrong with my script?
If $imgvalue is not an empty string, your first test is a syntax error, so I am assuming it is empty in the tests you are doing. In that case, your first test is equivalent to:
if [ :=1 ]
which is always true because :=1 is not an empty string. You probably meant to write:
if [ "$imgvalue" = 1 ]

Resources