I'm writing a bash script as the first part of an assignment. If the number of arguments is two, it's supposed to return the sum; if it's anything but two, it's supposed to return the error message and exit the script.
But even when I enter two commands, it still gives me the error message. Why is that? I wrote something very similar -- subtracting numbers -- a second ago and it worked fine.
#!/bin/bash
# This script reads two integers a, b and
# calculates the sum of them
# script name: add.sh
read -p "Enter two values:" a b
if [ $# -ne 2 ]; then
echo "Pass me two arguments!"
else
echo "$a+$b=$(($a+$b))"
fi
read reads from standard input, while the arguments ($1, $2, ...) whose count you are checking with $# are command line arguments that can be passed to your program when it is called.
I'd suggest
read -p "Enter two values: " a b additional_garbage
if [[ -z $b ]]; then # only have to test $b to ensure we have 2 values
The "additional_garbage" is to guard against the funny user who enters more than 2 values, and then $b is something like "2 3 4" and your arithmetic is broken.
And to guard against invalid octal numbers (for example if the user enters 08 and 09), enforce base-10
echo "$a+$b=$(( 10#$a + 10#$b ))"
Related
I am trying to count the seize of an parameter without the numbers and spaces, like if someone types in "Hello player1" it has to echo "11 characters". I have tried using ${#value} but this counts numbers and spaces.
if [ -z "$1" ]
then
echo "write at least 1 word"
else
for value in "$#"
do
echo "${value//[[:digit:]]/}"
done
echo ${#value}
fi
The result of the code
as you can see in the image it counts only the last parameter and counts the numbers what I don't want
Results of the second code
Break it into two steps.
set -- "abc 123" "d12"
for value do # in "$#" is default, don't need to say it
valueTrimmed=${value//[[:digit:][:space:]]/}
echo "The string '$value' has ${#valueTrimmed} valid characters"
done
...properly emits as output:
The string 'abc 123' has 3 valid characters
The string 'd12' has 1 valid characters
bash does not support nesting parameter expansions; you cannot do this in one step as a shell builtin (it could be done in a pipeline, but only with an unreasonably high performance cost).
I write this commands:
#!/bin/bash
echo "Enter values a and b (separate with space)"
read a b
echo $#
And I want to count how many arguments the user has entered. I try to count with $#, but the output is 0.
What's the problem? What I am doing wrong?
You may use an array to read complete line and count # of words:
read -p "Enter values (separate with space): " -ra arr
Enter values (separate with space): abc foo bar baz 123
Then print no of words:
echo "No of words: ${#arr[#]}"
No of words: 5
Here's how I'd probably do it, without thinking too much about it. It's hacky to use the dummy c variable, but I find bash array's even more awkward.
read -r a b c
if [[ $c ]]
then
echo "To much arguments"
elif [[ $a && $b ]]
echo "Correct - 2 arguments"
else
echo "Not enough arguments"
fi
I've been killing myself over this trying to figure it out, and I know it's probably super simple, so hoping a new pair of eyes can help. I have a Bourne shell (sh) script that I'm writing and it takes a list of integers as the input (and amount from 1 integer up, ideally I'd like to take both positive and negative ints). I'm trying to do an error check for the case if someone inputs something other than an integer. They could input "1 2 3 4 5 a" and it'd give an error, because the a is not an int.
I have an error check for no inputs that works, and I have code that does stuff to the list of integers themselves, but even when strings are given it still gets to my final code block.
I currently have a for loop to iterate through each item in the list of integers, then an if loop to give the error message if the argument in question isn't an int. I've tried a few different versions of this, but this is the most recent one so I've put it below.
for i in $#; do
if [ $i -ge 0 ] 2>/dev/null; then
echo "Invalid input: integers only."
exit 1
fi
done
#!/bin/sh
#
for i in "$#"
do
case "${i#[-+]}" in
0)
echo cannot be 0?
exit 1
;;
*[!0-9]* | '')
echo not int
exit 2
;;
esac
done
echo i\'m ok
This should work, for both positive and negative ints. And if you admit that 0 is an integer, just delete the first case.
Almost duplicate: BASH: Test whether string is valid as an integer?
And here is a good answer for posix. https://stackoverflow.com/a/18620446/7714132
You could use a regex:
my_script.sh
for i in $# ; do
if ! [[ "$i" =~ ^-?[0-9]+$ ]] ; then
echo "Invalid input: integers only."
exit 1
fi
done
Example:
$ sh my_script.sh 1 2 3 4
$ sh my_script.sh 1 2 -12
$ sh my_script.sh 1 2 -12-2
Invalid input: integers only.
$ sh my_script.sh 1 2 a b
Invalid input: integers only.
Explanation of the regex:
^: beginning of the string
-?: 0 or 1 times the character -
[0-9]+: 1 or more digit
$: end of the string
In POSIX sh, you can match your string against a glob with case:
#!/bin/sh
for i
do
case "$i" in
*[!0-9]*)
echo "Integers only" >&2
exit 1
esac
done
echo "Ok"
Here's how it runs:
$ ./myscript 1 2 3 4 5
Ok
$ ./myscript 1 2 3 4 5 a
Integers only
The problem with your approach is primarily that you're checking for success rather than failure: [ will fail when the input is invalid.
This program is supposed to call the first function, read-series and then pass the input of every iteration of the while loop to the even-odds function which would tell if the number was even or odd and make VARSUMODDS=the value of VARSUMODDS+input if it was odd or make VARPRODUCTEVENS=the value of VARSUMEVENS*input. Then it would print them out. I'm sure there are a thousand syntax errors here, so please, be brutal. Keep in mind that I just started learning this language and I just came to it knowing only C++ and Java a few days ago, so don't expect me to understand complex answers. Thanks!
#! /bin/bash
TMPDIR=${HOME}/tmpdir
echo "Enter an integer: "
VARSUMODDS=0
VARPRODUCTEVENS=0
function read-series() {
while read numbers ; do
echo "Enter an integer: "
even-odds $numbers
done
echo numbers > $TMPDIR/$$.temp
return 0;
}
function even-odds() {
evenp=$(( $1 % 2 ))
if [ $evenp -eq 0 ] ; then
$VARPRODUCTEVENS=$(($VARPRODUCTEVENS * $1))
return 0;
else
$VARSUMODDS=$(($VARSUMODDS + $1))
return 1;
fi
}
function reduce () {
echo -n "Sum of odds: "
echo VARSUMODDS
echo -n "Product of evens: "
echo VARPRODUCTEVENS
return 0;
}
read-series
#! /bin/bash
tmpdir=${HOME}/tmpdir
mkdir -p $tmpdir
odd_sum=0
even_product=1
numbers=()
read-series() {
while read -p "Enter an integer (q to quit): " number ; do
[[ $number == [Qq]* ]] && break
even-odds $number
numbers+=($number)
done
printf "%d\n" "${numbers[#]}" > $tmpdir/$$.temp
}
even-odds() {
if (( $1 % 2 == 0 )) ; then
(( even_product *= $1 ))
else
(( odd_sum += $1 ))
fi
}
reduce () {
echo "Sum of odds: $odd_sum"
echo "Product of evens: $even_product"
}
read-series
reduce
Notes:
make sure your tmpdir exists
no good seeding your product variable with 0
use an array to store the list of numbers
provide a way to break out of the input loop
in bash, the == operator within [[ ... ]] is a pattern matching operator.
you don't actually use your function return values anywhere, so I removed them
to declare a function, you don't need to use both the "function" keyword and the parentheses
use read -p to provide the prompt
use arithmetic expressions more widely
to assign a variable, don't use $ on the left-hand side.
to get the value, you must use $
let the system use upper-case variable names, you don't want to accidentally overwrite PATH for example.
avoid writing temp files unless you really need them (for logging or auditing)
printf re-uses the format string until all the arguments are consumed. this is very handy for printing the contents of an array.
semi-colons are optional
You should initialize VARPRODUCTEVENS to 1, because multiplying anything by 0 produces 0.
$ should not be put before the variable being assigned in an assignment statement.
You can use the -p option to read to specify a prompt
You're writing to $$.temp after the loop is done. numbers will be empty then, so you're not writing anything to the file. If you want to record all the numbers that were entered, you must do that inside the loop, and use >> to append to the file instead of overwriting it.
There's no reason to use return in your functions -- nothing tests the exit status. And non-zero is usually used to mean there was an error.
You defined a function reduce to print the results, but never called it
You need to put $ before the variable names on all your echo lines.
Don't put function before function definitions; it's allowed, but not required, and not portable (it's a bash extension).
#! /bin/bash
TMPDIR=${HOME}/tmpdir
VARSUMODDS=0
VARPRODUCTEVENS=1
read-series() {
while read -p "Enter an integer: " numbers ; do
even-odds $numbers
echo $numbers >> $TMPDIR/$$.temp
done
}
even-odds() {
evenp=$(( $1 % 2 ))
if [ $evenp -eq 0 ] ; then
VARPRODUCTEVENS=$(($VARPRODUCTEVENS * $1))
else
VARSUMODDS=$(($VARSUMODDS + $1))
fi
}
reduce () {
echo ''
echo -n "Sum of odds: "
echo $VARSUMODDS
echo -n "Product of evens: "
echo $VARPRODUCTEVENS
}
read-series
reduce
If you run this script stand-alone, you have to end your input with CTRL-d. Here are the problems:
VARPRODUCTEVENS=0
has to be
VARPRODUCTEVENS=1
or your product will always be zero.
echo numbers > $TMPDIR/$$.temp
Seems to have no useful purpose. You are putting the string "numbers" into the file. If you use $numbers it still appears to have no purpose. You would be putting the single last number from the read into the file. From the use "number" may be a better name than "numbers"
$VARPRODUCTEVENS=$(($VARPRODUCTEVENS * $1))
and
$VARSUMODDS=$(($VARSUMODDS + $1))
has to be
VARPRODUCTEVENS=$(($VARPRODUCTEVENS * $1))
and
VARSUMODDS=$(($VARSUMODDS + $1))
Having $VARSUMODDS on the left of the assignment will try to assign to the variable named "1" (the value of $VARSUMODDS).
There is no call to reduce, so you see no results. I assume you want that at the end.
Your return statements are unnecessary, and probably not doing what you intended. You are basically setting the exit status, and non-zero implies failure.
How do I find the number of arguments passed to a Bash script?
This is what I have currently:
#!/bin/bash
i=0
for var in "$#"
do
i=i+1
done
Are there other (better) ways of doing this?
The number of arguments is $#
Search for it on this page to learn more:
http://tldp.org/LDP/abs/html/internalvariables.html#ARGLIST
#!/bin/bash
echo "The number of arguments is: $#"
a=${#}
echo "The total length of all arguments is: ${#a}: "
count=0
for var in "$#"
do
echo "The length of argument '$var' is: ${#var}"
(( count++ ))
(( accum += ${#var} ))
done
echo "The counted number of arguments is: $count"
echo "The accumulated length of all arguments is: $accum"
to add the original reference:
You can get the number of arguments from the special parameter $#. Value of 0 means "no arguments". $# is read-only.
When used in conjunction with shift for argument processing, the special parameter $# is decremented each time Bash Builtin shift is executed.
see Bash Reference Manual in section 3.4.2 Special Parameters:
"The shell treats several parameters specially. These parameters may only be referenced"
and in this section for keyword $# "Expands to the number of positional parameters in decimal."
Below is the easy one -
cat countvariable.sh
echo "$#" | awk '{print NF}'
Output :
#./countvariable.sh 1 2 3 4 5 6
6
#./countvariable.sh 1 2 3 4 5 6 apple orange
8