0

I have two scripts that work together by themselves and I have no idea how to make them work together

the task is to make a bash script accept operators and a list of integers and having the command perform the operations.

I basically need to be able to use the script as such

test.sh add 1 2 3
5
test.sh sub 4 2 1
1

the first script I have is

for var in $@
do

sum=$(( sum + var ))
done
echo $sum

second script is

if [ $operator = add ]; then
add= echo $(($a+$b))

elif 
13
  • (1) Never say for var in $@.  Either shorten it to for var or change it to for var in "$@" (note the quotes).  (2) What are you asking?  You say you want to do test.sh add 1 2 3, but then you say you want to do test.sh 1 2 3.  (3) Your second script is nearly what you want.  Your first script adds all the arguments; what part of that do you want? – G-Man Says 'Reinstate Monica' Feb 12 at 6:26
  • Ah my mistake, I meant to put test.sh add 1 2 3 for both the first script allows it to do``` test.sh 1 2 3 ``` and I need it to be able to add numbers together to get an output so my example was test.sh add 1 2 3 to get 5 – Dooooofy Feb 12 at 6:34
  • ``` if [ $operator = add ]; then add= echo $(( sum + var )) fi echo $add ``` pops up an error line 7: [: =: unary operator expected I'm not sure what is means – Dooooofy Feb 12 at 7:01
  • 1
    What is your initial task? Why do you want to do this? Do you want to learn scripting (good, we can help, but probably there are easier ways to achieve it) or do you want to sum up numbers (you don't need to script it yourself, rather use programs doing it for you). – pLumo Feb 12 at 7:19
  • 1
    I'm just saying it is difficult to help you if we don't exactly know what you want to achieve, especially because your second script's purpose and how it should work is unclear. – pLumo Feb 12 at 7:35
1

A script that picks out its first command line argument into a separate variable called op and then removes this from the list of command line arguments:

#!/bin/sh

op=$1
shift

If this script is called with add 1 2 3 as arguments, then the code would assign the string add to op and would leave 1 2 3 as the only three command line arguments in "$@".

We may then loop over the remaining command line arguments:

#!/bin/sh

op=$1
shift

for arg do
    # some commands should go here
done

The added loop will make sure that the variable arg will take the value of each of the remaining command line arguments in turn. We could also have written the start of the loop like for arg in "$@"; do ... but it's more to type and programmers sometimes forget to add the quotes around $@.

Depending on the value $op one of several operations may happen in the loop. We may use a simple test to see what $op is and carry out the correct operation. In any case, some value is being accumulated in acc below, and this acc value should (according to the way the commands are presented in the question) be initialized to the first number, and that number needs to be shifted off the list of arguments too.

#!/bin/sh

op=$1
shift

acc=$1
shift

for arg do
    case $op in
        add) acc=$(( acc + arg )) ;;
        sub) acc=$(( acc - arg )) ;;
        mul) acc=$(( acc * arg )) ;;
        div) acc=$(( acc / arg )) ;; # note: integer division here
        *)
            printf 'Unknown operation: %s\n' "$op" >&2
            exit 1
    esac
done

printf '%s\n' "$acc"

Testing this script after saving it and making it executable:

$ ./script add 1 2 3
6
$ ./script sub 4 2 1
1
$ ./script xor a b c
Unknown operation: xor
$ ./script add
script[7]: shift: nothing to shift

Note the two last invocations. The last one failed to provide any numbers, so the second shift fails. The second to last invocation notices that the operation is unknown and terminates prematurely.

By changing the assignment to op and the initialization of acc slightly, we can make the error messages slightly more useful:

#!/bin/sh

op=${1:?Expected operator}
shift

acc=${1:?Expected number}
shift

for arg do
    case $op in
        add) acc=$(( acc + arg )) ;;
        sub) acc=$(( acc - arg )) ;;
        mul) acc=$(( acc * arg )) ;;
        div) acc=$(( acc / arg )) ;; # note: integer division here
        *)
            printf 'Unknown operation: %s\n' "$op" >&2
            exit 1
    esac
done

printf '%s\n' "$acc"

Testing again:

$ ./script
script[3]: 1: Expected operator
$ ./script add
script[6]: 1: Expected number
$ ./script add 1 2
3

The exact formatting of these error messages may differ depending on what shell acts as /bin/sh on your system.

2
  • Might be worth noting that if /bin/sh happens to be based on ksh, bash or zsh (as opposed to ash-based shells, bosh, or yash) then that script has an arbitrary code injection vulnerability. You'll also get variations in behaviour if operands are floating point. A proper programming language (even awk) would be better suited than a shell here. – Stéphane Chazelas Mar 5 at 12:22
  • @StéphaneChazelas Thanks! I'll insert a basic "check that string is an integer" test later today. – Kusalananda Mar 5 at 12:37
0

Your first script is looping through $@, if you want to add an operand, you can save the first argument (op=$1) and then use shift.

from man bash:

shift [n]
The positional parameters from n+1 ... are renamed to $1 .... Parameters represented by the numbers $# down to $#-n+1 are unset. n must be a non-negative number less than or equal to $#. If n is 0, no parameters are changed. If n is not given, it is assumed to be 1. If n is greater than $#, the positional parameters are not changed. The return status is greater than zero if n is greater than $# or less than zero; otherwise 0.

Initialize $result with the first argument, then use shift again.

Also, make sure to quote $@ when running the loop (read here).

#!/bin/bash
op=$1
shift
result=$1
shift
for var in "$@"; do
  result=$(( result $op var ))
done
echo "$result"

You could also do the shift in one step:

op=$1
result=$2
shift 2

Run test.sh + 1 2 3 or test.sh '*' 1 2 3 ...

('*' must be quoted, to not expand to filenames).


However, I would rather use bc and $IFS instead of a loop and $(( ... ))) for this:

#!/bin/bash
OLDIFS="$IFS"
IFS=$1
shift
bc <<< "scale=5; $*"
IFS=$OLDIFS

See also:

"$*" expands to a single word "$1c$2c...". Usually c is a space, but it's actually the first character of IFS, so it can be anything you choose.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.