I'm trying to do a script that is used with two arguments - a file and an integer. It should check if the arguments are valid, otherwise exit with 1. Then it should either return 0 if the file is smaller than second argument, or echo size of the file to stdout. The script keeps returning value 123 instead of 1 or 0. Where is the problem? Thanks.
#!/bin/bash
if [ $# -eq 2 ];
then
if test $2 -eq $2 > /dev/null 2>&1
then
if [ -f $1 ];
then
if [ $(stat -c %s $1) -ge $2 ];
then
echo $(stat -c %s $1)
else
exit 0
fi
else
exit 1
fi
else
exit 1
fi
else
echo 042f9
exit 1
fi
I do not know where the "123" output comes from, but I would do it like this:
#!/bin/bash
# Must have 2 arguments
if [[ $# -ne 2 ]]
then
printf "042f9\n"
exit 1
fi
# File must exist
if [[ ! -f "$1" ]]
then
exit 1
fi
# File size > $2 check
filesize=$(stat -c %s "$1")
if [[ $filesize -ge $2 ]]
then
printf "%d" "$filesize"
else
exit 1
fi
A couple notes for your scripts (IMHO):
Like Mat mentioned in the comments, test 1 condition and exit right away. When I read your script, I had to go to the end to see what happens if the number of arguments is wrong. Logically there is nothing wrong with your code, it is just making it easier to read.
For bash, use [[ ]] to test if conditions.
I try never to call a function or command twice. That is why I stored the result of the stat command in a variable. If you use it more than once, store it, do not call the command again.
No need for ; since you put your then on the next line anyway.
Always double-quote your variables, especially if they are filenames. Weird filenames break so many scripts!
Finally use printf instead of echo. For simple cases, its the same, but echo does have some issues (https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo).
Possible return values:
the size of the file, and the exit value is 0 ($?). The file is larger than argument 2 value.
"042f9", and the exit value is 1 ($?). Arguments error.
nothing, and the exit value is 1 ($?). Missing file error, or the file is smaller than argument 2 value.
I have a simple script in Bash to read a number in a file and then compare it with a different threshold. The output is this:
: integer expression expected
: integer expression expected
OK: 3
My code is this:
#!/bin/bash
wget=$(wget http://10.228.28.8/ -O /tmp/wget.txt 2>/dev/null)
output=$(cat /tmp/wget.txt | awk 'NR==6')
#output=7
echo $output
if [ $output -ge 11 ];then
echo "CRITICAL: $output"
exit 2
elif [ $output -ge 6 ] && [ $output -lt 11 ];then
echo "WARNING: $output"
exit 1
else
echo "OK: $output"
exit 0
fi
rm /tmp/wget.txt
I know what is the problem, I know that I'm reading a string and I try to compare a int. But I don't know how can I do to read this file and convert the number to read in a int var..
Any ideas?
The problem occurs when $output is the empty string; whether or not you quote the expansion (and you should), you'll get the integer expression required error. You need to handle the empty string explictly, with a default value of zero (or whatever default makes sense).
wget=$(wget http://10.228.28.8/ -O /tmp/wget.txt 2>/dev/null)
output=$(awk 'NR==6' < /tmp/get.txt)
output=${output:-0}
if [ "$output" -ge 11 ];then
echo "CRITICAL: $output"
exit 2
elif [ "$output" -ge 6 ];then
echo "WARNING: $output"
exit 1
else
echo "OK: $output"
exit 0
fi
(If you reach the elif, you already know the value of $output is less than 11; there's no need to check again.)
The problem also occurs, and is consistent with the error message, if output ends with a carriage return. You can remove that with
output=${output%$'\r'}
There are a couple of suggestions from my side regarding your code.
You could explicitly tell bash the output is an integer
declare -i output # See [1]
Change
output=$(cat /tmp/wget.txt | awk 'NR==6') # See [2]
may be better written as
output=$(awk 'NR==6' /tmp/wget.txt )
Change
if [ $output -ge 11 ]
to
if [ "0$output" -ge 11 ] # See [4]
or
if (( output >= 11 )) # Better See [3]
References
Check bash [ declare ].
Useless use of cat. Check [ this ]
Quoting [ this ] answer :
((...)) enable you to omit the dollar signs on integer and array variables and include spaces around operators for readability. Also empty variable automatically defaults to 0 in such a statement.
The zero in the beginning of "0$output" help you deal with empty $output
Interesting
Useless use of cat is a phrase that has been resounding in SO for long. Check [ this ]
[ #chepner ] has dealt with the empty output fiasco using [ bash parameter expansion ] in his [ answer ], worth having a look at.
A simplified script:
#!/bin/bash
wget=$(wget http://10.228.28.8/ -O /tmp/wget.txt 2>/dev/null)
output=$(awk 'NR==6' </tmp/wget.txt )
output="$(( 10#${output//[^0-9]} + 0 ))"
(( output >= 11 )) && { echo "CRITICAL: $output"; exit 2; }
(( output >= 6 )) && { echo "WARNING: $output"; exit 1; }
echo "OK: $output"
The key line to cleanup any input is:
output="$(( 10#${output//[^0-9]} + 0 ))"
${output//[^0-9]} Will leave only digits from 0 to 9 (will remove all non-numeric chars).
10#${output//[^0-9]} Will convert output to a base 10 number.
That will correctly convert numbers like 0019
"$(( 10#${output//[^0-9]} + 0 ))" Will produce a zero for a missing value.
Then the resulting number stored in output will be compared to limits and the corresponding output will be printed.
In BASH, It is a good idea to use double brackets for strings:
if [[ testing strings ]]; then
<whatever>
else
<whatever>
fi
Or double parenthesis for integers:
if (( testing ints )); then
<whatever>
else
<whatever>
fi
For example try this:
var1="foo bar"
if [ $var1 == 'foo bar' ]; then
echo "ok"
fi
Result:
$ bash: [: too many arguments
Now, this:
var2="foo bar"
if [[ $a == "foo bar" ]]; then
echo "ok"
fi
Result:
ok
For that, your code in BASH:
if [[ $output -ge 11 ]]; then
echo "CRITICAL: $output"
exit 2
elif [[ $output -ge 6 ]]; then
echo "WARNING: $output"
exit 1
else
echo "OK: $output"
exit 0
fi
I want to make sure my bash script can correctly detect user's input argument. Specifically, user can only pass 1 or 2 or 3 into the script, otherwise the script will be terminated. I wrote the following code:
#!/bin/bash
for args in $#
do
echo $args
done
if [ "$#" != 1 ] && [ "$#" -ne 1 ]; then
echo "Illegal number of parameters"
exit 1
fi
This script can only capture when user does not give any input, but cannot check whether user indeed input the number 1 not other values.
By the way, I am not sure how to express "input argument can accept number 1 or 2 or 3".
$# is an integer, so you have to use integer comparison. You can for example say:
if [ "$#" -ne 1 ]; then
echo "illegal number of parameters"
exit 1
fi
To check that the parameter is either 1, 2 or 3, you can use this regular expression (see something related):
if [[ ! $1 =~ ^(1|2|3)$ ]]; then
echo "the number is not 1, 2 or 3"
exit 1
fi
To express "input argument can accept number 1 or 2 or 3" I would for example say "we can just accept the argument being either 1, 2 or 3".
First off you can detect if the string is null or empty simply by doing the following:
if [ -z "$1" ]
then
echo "Argument $1 contains nothing"
fi
That I would say is your first step, and will allow you to filter out args that have no content.
Following on from that, you'd most likely need to do some comparison work on $1, $2 & $3
I'll just check something and come back to this in a moment.
Update
Just had to go find one of my scripts and check something... :-)
One way I've handled the checking of parms in the past is something like the following
#!/bin/sh
while [ $# -gt 0 ] || [ "$#" -le 4 ]; do
case "$1" in
*[!1-9]*) echo "Text: $1";;
*) echo "Number: $1"
esac
case "$2" in
*[!1-9]*) echo "Text: $2";;
*) echo "Number: $2"
esac
case "$3" in
*[!1-9]*) echo "Text: $3";;
*) echo "Number: $3"
esac
shift
done
Basically a simple regex, if I have more than 0 parameters or less than 4 parameters then I allow it through to a case statement, which then checks the content of each parameter.
This one just has an echo in, but you could just as easy set some flags, and then decide how to continue based on those flags.
For simple range checking however, you might just want to use a one liner similar to the following:
if [[ $# -gt 0 && $# -lt 4 ]]; then echo "Correct number of parameters"; fi
Again setting a flag to use later rather than echoing the results.
I assume you mean the input can only be 1, 2 or 3, so using a case statement is the best way. $1 is the variable that stores your argument, if it is equal to 1 case will execute the code in the block corresponding to the value 1 and so on.
case "$1" in
1) ...
;;
2) ...
;;
3) ...
;;
*) echo "Invalid argument"
;;
esac
We have a variable with a value in it. My requirement is if the length of the variable is greater than 0(zero) do something else do another thing
Step1: I have a variable with some value say
abc="asdfg"
Step2: Determine the length of the variable
xyz=`echo ${#abc}`
Step3: Now I use a If loop to determine if the variable has value greater than 0
if [[$xyz -gt 0]]; then
echo "success"
else
echo "fail"
fi
I am having a problem at step 3. it does not give me a success message. need assistance
You can check if a variable is not zero length with -n
if [ -n $abc ]; then
echo "success"
fi
if [[$xyz -gt 0]]; then
should be:
if [[ $xyz -gt 0 ]]; then
You need spaces between the brackets and parameters.
You can do it easily like this:
VAR=""
if [ "$VAR" ]; then echo Full; fi
VAR="a"
if [ "$VAR" ]; then echo Full; fi
Full
You are missing some spaces around your square brackets.
A short and fast solution to do this is to use ${#var} directly in an if-test:
if (( ${#abc} )); then
# ...
fi
This removes the unnecessary command-substituion that you have there and tests directly on the length. If the length is zero then the test fails.
I am trying to write a script in bash that check the validity of a user input.
I want to match the input (say variable x) to a list of valid values.
what I have come up with at the moment is:
for item in $list
do
if [ "$x" == "$item" ]; then
echo "In the list"
exit
fi
done
My question is if there is a simpler way to do this,
something like a list.contains(x) for most programming languages.
Say list is:
list="11 22 33"
my code will echo the message only for those values since list is treated as an array and not a string,
all the string manipulations will validate 1 while I would want it to fail.
[[ $list =~ (^|[[:space:]])$x($|[[:space:]]) ]] && echo 'yes' || echo 'no'
or create a function:
contains() {
[[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && exit(0) || exit(1)
}
to use it:
contains aList anItem
echo $? # 0: match, 1: failed
how about
echo $list | grep -w -q $x
you could either check the output or $? of above line to make the decision.
grep -w checks on whole word patterns. Adding -q prevents echoing the list.
Matvey is right, but you should quote $x and consider any kind of "spaces" (e.g. new line) with
[[ $list =~ (^|[[:space:]])"$x"($|[[:space:]]) ]] && echo 'yes' || echo 'no'
so, i.e.
# list_include_item "10 11 12" "2"
function list_include_item {
local list="$1"
local item="$2"
if [[ $list =~ (^|[[:space:]])"$item"($|[[:space:]]) ]] ; then
# yes, list include item
result=0
else
result=1
fi
return $result
}
end then
`list_include_item "10 11 12" "12"` && echo "yes" || echo "no"
or
if `list_include_item "10 11 12" "1"` ; then
echo "yes"
else
echo "no"
fi
Note that you must use "" in case of variables:
`list_include_item "$my_list" "$my_item"` && echo "yes" || echo "no"
IMHO easiest solution is to prepend and append the original string with a space and check against a regex with [[ ]]
haystack='foo bar'
needle='bar'
if [[ " $haystack " =~ .*\ $needle\ .* ]]; then
...
fi
this will not be false positive on values with values containing the needle as a substring, e.g. with a haystack foo barbaz.
(The concept is shamelessly stolen form JQuery's hasClass()-Method)
You can use (* wildcards) outside a case statement, too, if you use double brackets:
string='My string';
if [[ $string == *My* ]]
then
echo "It's there!";
fi
If it isn't too long; you can just string them between equality along a logical OR comparison like so.
if [ $ITEM == "item1" -o $ITEM == "item2" -o $ITEM == "item3" ]; then
echo In the list
fi
I had this exact problem and while the above is ugly it is more obvious what is going on than the other generalized solutions.
If your list of values is to be hard-coded in the script, it's fairly simple to test using case. Here's a short example, which you can adapt to your requirements:
for item in $list
do
case "$x" in
item1|item2)
echo "In the list"
;;
not_an_item)
echo "Error" >&2
exit 1
;;
esac
done
If the list is an array variable at runtime, one of the other answers is probably a better fit.
There's a cleaner way to check if string is in the list:
if [[ $my_str = #(str1|str2|str3) ]]; then
echo "string found"
fi
Consider exploiting the keys of associative arrays. I would presume this outperforms both regex/pattern matching and looping, although I haven't profiled it.
declare -A list=( [one]=1 [two]=two [three]='any non-empty value' )
for value in one two three four
do
echo -n "$value is "
# a missing key expands to the null string,
# and we've set each interesting key to a non-empty value
[[ -z "${list[$value]}" ]] && echo -n '*not* '
echo "a member of ( ${!list[*]} )"
done
Output:
one is a member of ( one two three )
two is a member of ( one two three )
three is a member of ( one two three )
four is *not* a member of ( one two three )
If the list is fixed in the script, I like the following the best:
validate() {
grep -F -q -x "$1" <<EOF
item 1
item 2
item 3
EOF
}
Then use validate "$x" to test if $x is allowed.
If you want a one-liner, and don't care about whitespace in item names, you can use this (notice -w instead of -x):
validate() { echo "11 22 33" | grep -F -q -w "$1"; }
Notes:
This is POSIX sh compliant.
validate does not accept substrings (remove the -x option to grep if you want that).
validate interprets its argument as a fixed string, not a regular
expression (remove the -F option to grep if you want that).
Sample code to exercise the function:
for x in "item 1" "item2" "item 3" "3" "*"; do
echo -n "'$x' is "
validate "$x" && echo "valid" || echo "invalid"
done
I find it's easier to use the form echo $LIST | xargs -n1 echo | grep $VALUE as illustrated below:
LIST="ITEM1 ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | xargs -n1 echo | grep -e \"^$VALUE`$\" ]; then
...
fi
This works for a space-separated list, but you could adapt it to any other delimiter (like :) by doing the following:
LIST="ITEM1:ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | sed 's|:|\\n|g' | grep -e \"^$VALUE`$\"`" ]; then
...
fi
Note that the " are required for the test to work.
Thought I'd add my solution to the list.
# Checks if element "$1" is in array "$2"
# #NOTE:
# Be sure that array is passed in the form:
# "${ARR[#]}"
elementIn () {
# shopt -s nocasematch # Can be useful to disable case-matching
local e
for e in "${#:2}"; do [[ "$e" == "$1" ]] && return 0; done
return 1
}
# Usage:
list=(11 22 33)
item=22
if elementIn "$item" "${list[#]}"; then
echo TRUE;
else
echo FALSE
fi
# TRUE
item=44
elementIn $item "${list[#]}" && echo TRUE || echo FALSE
# FALSE
The shell built-in compgen can help here. It can take a list with the -W flag and return any of the potential matches it finds.
# My list can contain spaces so I want to set the internal
# file separator to newline to preserve the original strings.
IFS=$'\n'
# Create a list of acceptable strings.
accept=( 'foo' 'bar' 'foo bar' )
# The string we will check
word='foo'
# compgen will return a list of possible matches of the
# variable 'word' with the best match being first.
compgen -W "${accept[*]}" "$word"
# Returns:
# foo
# foo bar
We can write a function to test if a string equals the best match of acceptable strings. This allows you to return a 0 or 1 for a pass or fail respectively.
function validate {
local IFS=$'\n'
local accept=( 'foo' 'bar' 'foo bar' )
if [ "$1" == "$(compgen -W "${accept[*]}" "$1" | head -1)" ] ; then
return 0
else
return 1
fi
}
Now you can write very clean tests to validate if a string is acceptable.
validate "blah" || echo unacceptable
if validate "foo" ; then
echo acceptable
else
echo unacceptable
fi
Prior answers don't use tr which I found to be useful with grep. Assuming that the items in the list are space delimited, to check for an exact match:
echo $mylist | tr ' ' '\n' | grep -F -x -q "$myitem"
This will return exit code 0 if the item is in the list, or exit code 1 if it isn't.
It's best to use it as a function:
_contains () { # Check if space-separated list $1 contains line $2
echo "$1" | tr ' ' '\n' | grep -F -x -q "$2"
}
mylist="aa bb cc"
# Positive check
if _contains "${mylist}" "${myitem}"; then
echo "in list"
fi
# Negative check
if ! _contains "${mylist}" "${myitem}"; then
echo "not in list"
fi
Late to the show? Following very easy variant was not clearly mentioned yet. I use case for checking simple lists, which is a general Bourne Shell idiom not relying on anything external nor extended:
haystack='a b c'
needle='b'
case " $haystack " in (*" $needle "*) :;; (*) false;; esac
Please note the use of the separator (here: SPC) to correcyly delimit the pattern: At the beginning and end of " $haystack " and likewise in the test of " $needle ".
This statement returns true ($?=0) in case $needle is in $haystack, false otherwise.
Also you can test for more than one $needle very easily. When there are several similar cases like
if (haystack.contains(needle1)) { run1() } elif (haystack.contains(needle2)) { run2() } else { run3() }
you can wrap this into the case, too:
case " $haystack " in (*" $needle1 "*) run1;; (*" $needle2 "*) run2;; (*) run3;; esac
and so on
This also works for all lists with values which do not include the separator itself, like comma:
haystack=' a , b , c '
needle=' b '
case ",$haystack," in (*",$needle,"*) :;; (*) false;; esac
Note that if values can contain anything including the separator sequence (except NUL, as shells do not suport NUL in variables as you cannot pass arguments containing NUL to commands) then you need to use arrays. Arrays are ksh/bashisms and not supported by "ordinary" POSIX/Bourne shells. (You can work around this limitation using $# in POSIX-Shells, but this is something completely different than what was aked here.)
Can the (*) false part be left away?
No, as this is the critical return value. By default case returns true.
Yes if you do not need the return value and put your processing at the location of the :
Why the :;;
We could also write true;;, but I am used to use : instead of true because it is shorter and faster to type
Also I consider not writing anything bad practice, as it is not obvious to everybody that the default return value of case is true.
Also "leaving out" the command usually indicates "something was forgotten here". So putting a redundant ":" there clearly indicates "it is intended to do nothing else than return true here".
In bash you can also use ksh/bashisms like ;& (fallthroug) or ;;& (test other patterns) to express if (haystack.contains(needle1)) { run1(); }; if (haystack.contains(needle2)) { run2(); }
Hence usually case is much more maintainable than other regex constructs. Also it does not use regex, it only use shell patterns, which might even be faster.
Reusable function:
: Needle "list" Seperator_opt
NeedleListSep()
{
if [ 3 -gt $# ];
then NeedleListSep "$1" "$2" " ";
else case "$3$2$3" in (*"$3$1$3"*) return 0;; esac; return 1;
fi;
}
In bash you can simplify this to
: Needle "list" Seperator_opt
NeedleListSep()
{
local s="${3-" "}";
case "$s$2$s" in (*"$s$1$s"*) return 0;; esac; return 1;
}
Use like this
Test() {
NeedleListSep "$1" "a b c" && echo found $1 || echo no $1;
NeedleListSep "$1" "a,b,c" ',' && echo found $1 || echo no $1;
NeedleListSep "$1" "a # b # c" ' # ' && echo found $1 || echo no $1;
NeedleListSep "$1" "abc" '' && echo found $1 || echo no $1;
}
Test a
Test z
As shown above, this also works for degerated cases where the separator is the empty string (so each character of the list is a needle). Example:
Test
returns
no
no
no
found
As the empty string is cleary part of abc in case your separator is the empty string, right?
Note that this function is Public Domain as there is absolutely nothing to it which can be genuinely copyrighted.
An alternative solution inspired by the accepted response, but that uses an inverted logic:
MODE="${1}"
echo "<${MODE}>"
[[ "${MODE}" =~ ^(preview|live|both)$ ]] && echo "OK" || echo "Uh?"
Here, the input ($MODE) must be one of the options in the regular expression ('preview', 'live', or 'both'), contrary to matching the whole options list to the user input. Of course, you do not expect the regular expression to change.
Examples
$ in_list super test me out
NO
$ in_list "super dude" test me out
NO
$ in_list "super dude" test me "super dude"
YES
# How to use in another script
if [ $(in_list $1 OPTION1 OPTION2) == "NO" ]
then
echo "UNKNOWN type for param 1: Should be OPTION1 or OPTION2"
exit;
fi
in_list
function show_help()
{
IT=$(CAT <<EOF
usage: SEARCH_FOR {ITEM1} {ITEM2} {ITEM3} ...
e.g.
a b c d -> NO
a b a d -> YES
"test me" how "test me" -> YES
)
echo "$IT"
exit
}
if [ "$1" == "help" ]
then
show_help
fi
if [ "$#" -eq 0 ]; then
show_help
fi
SEARCH_FOR=$1
shift;
for ITEM in "$#"
do
if [ "$SEARCH_FOR" == "$ITEM" ]
then
echo "YES"
exit;
fi
done
echo "NO"
Assuming TARGET variable can be only 'binomial' or 'regression', then following would do:
# Check for modeling types known to this script
if [ $( echo "${TARGET}" | egrep -c "^(binomial|regression)$" ) -eq 0 ]; then
echo "This scoring program can only handle 'binomial' and 'regression' methods now." >&2
usage
fi
You could add more strings into the list by separating them with a | (pipe) character.
Advantage of using egrep, is that you could easily add case insensitivity (-i), or check more complex scenarios with a regular expression.
This is almost your original proposal but almost a 1-liner. Not that complicated as other valid answers, and not so depending on bash versions (can work with old bashes).
OK=0 ; MP_FLAVOURS="vanilla lemon hazelnut straciatella"
for FLAV in $MP_FLAVOURS ; do [ $FLAV == $FLAVOR ] && { OK=1 ; break; } ; done
[ $OK -eq 0 ] && { echo "$FLAVOR not a valid value ($MP_FLAVOURS)" ; exit 1 ; }
I guess my proposal can still be improved, both in length and style.
Simple oneliner...
if [[ " 11 22 33 " == *" ${x} "* ]]; then echo "${x} is in the list"; fi;
Add before fi: else echo "${x} is NOT in the list";
The script below implements contains function for a list.
function contains {
local target=$1
shift
printf '%s\n' "$#" | grep -x -q "$target"
out=$?
(( out = 1 - out ))
return $out
}
If you convert a string based on white space into a list and use it, it seems to be solved as follows.
list="11 22 33"
IFS=" " read -ra parsed_list <<< "$list"
# parsed_list would be ("11" "22" "33")
contains "11" "${parsed_list[#]}"
echo $? # 1
contains "22" "${parsed_list[#]}"
echo $? # 1
contains "1" "${parsed_list[#]}"
echo $? # 0
contains "11 22" "${parsed_list[#]}"
echo $? # 0