0

I am trying to make a bash script that takes in a number from command line (between 1 and 50). If the number is even then it divides the number by 2, and if the number is odd then it multiplies it by 3 and adds 1. The number then goes back through the functions until the number 1 is reached. Here is what I have so far

#!/bin/bash

//this looks to see if the command line argument, $1, is between 1 and 50
if ["$1" -gt 1 ] && [ "$1" -lt 50]
then
//if $1 is between 1 and 50, it goes into a for loop when it looks to see if it is even or odd. It keeps going until the number 1 is reached, and prints out each number until then
for i in $1; do
    //if $1 is even
    if [i%2 -eq 0]
    then
        i/2 == i  
        echo "i,"
    //if $1 is even
    else 
        3*i+1 == i
        echo "i,"
    fi
done
fi 

When I run the code, I get an error.

$ ./q1.sh 5

./q1.sh: line 3: [5: command not found

I have looked online to see what the error means but cant find an answer. Can someone please help me.

Benjamin W.
  • 33,075
  • 16
  • 78
  • 86
  • 3
    You always need a space after a `[`. Remember, `[` is just a command, not syntax! It now tries to run the command `[5`, which doesn't exist. Hence the error. Also, `//` are not valid comment characters; use `#`. – Martin Tournoij Mar 13 '16 at 22:56
  • 1
    Also I think those arithmetic expressions (`i/2 == i`) won't do what you expect them to do. – Brian Cain Mar 13 '16 at 22:57
  • Indeed ... The more I look at this, the more things that I find wrong with it :-/ There is an error on almost every line! I don't want to be rude, but have you actually tried learning shell scripting? Because right now it looks like you're inventing syntax on the spot by importing what you know from some other language. That's not how to learn shell scripting (...or anything else, for that matter...). I strongly suggest just following a tutorial or book on shell scripting first. – Martin Tournoij Mar 13 '16 at 22:59
  • I just started learning bash, and the only programming knowledge I have is from java and c/c++. I am doing a simple exercise to help me understand command line arguments, and simple operators. When I look online I find 5 different ways to do everything. Do you have a good resource that I could look at, or help me correct this please – lawtonhself Mar 13 '16 at 23:02
  • 2
    Please don't edit your question to remove the source of the errors you get, or the question doesn't make sense any longer. Case in point: the space after the `[` should *not* be inserted, even though now you know that it should be there. A good Bash resource is the [BashGuide](http://mywiki.wooledge.org/BashGuide); I'd recommend to steer clear of the "Advanced Bash-Scripting Guide" at least for a while. – Benjamin W. Mar 13 '16 at 23:46
  • Please don't edit the question after you've got answers such that your edits render the answers invalid. Your original code had multiple spacing problems (`if ["$1" -gt 1 ] && [ "$1" -lt 50]; then for i in $1; do if [i%2 -eq 0] …` that have since been fixed. That's not good. You also used an idiosyncratic comment convention, using `//` instead of `#`, to add comments. That's not the best idea in shell scripts. One current incarnation of the code has `if [ "$1" -gt 1 ] -a [ "$1" -lt 50 ]` — you can't use the `-a` like that; it would have to be `if [ "$1" -gt 1 -a "$1" -lt 50 ]` instead. – Jonathan Leffler Mar 13 '16 at 23:50
  • Note that the mathematics of the problem you're solving is called the [Collatz Conjecture](https://en.wikipedia.org/wiki/Collatz_conjecture). – Jonathan Leffler Mar 14 '16 at 00:07

2 Answers2

2

Just looking at your requirements, I would solve this as follows:

#!/bin/bash

# Assign positional parameter
num="$1"

if (( num > 1 && num < 50 )); then
    while (( num != 1 )); do
        if (( num % 2 == 0 )); then # Number is even
            (( num /= 2))           # Compound assignment: same as num = num / 2
        else                        # Number is odd
            (( num = num * 3 + 1 ))
        fi
        printf "Number is %d\n" "$num"
    done
fi

This makes heavy use of arithmetic expressions (( ... )) for conditions and calculations. In fact, I don't use the test construct [ ... ] ever. If I use this, I can even assign within the expression with, for example, (( num = num * 2 )) instead of num=$(( num * 2 )).

Notice how the while loop checks if we have reached the value 1 yet. The way you tried it, with for i in $1, is just a (complicated) way to assign $1 to i, but the loop is executed just once.

Things from your code that won't work

  • Comments start with #, not with //.
  • The test construct [ ... ] requires spaces: [ "$1" -gt 1 ] etc.
  • The for loop just assigns $1 to i, nothing else.
  • [ i%2 -eq 0 ] compares the literal i%2, and not the result of the variable i modulo 2; you'd need [ $(( i%2 )) -eq 0 ] for that, or (( i%2 == 0 )).
  • The assignment i/2 == i will try to execute the command i/2 with the arguments == and i – with little success. You want i=$(( i/2 )) or (( i /= 2 )).
  • echo "i," just prints i,. You want echo "$i,".
  • For the assignment 3*i+1 == i, see above.
Benjamin W.
  • 33,075
  • 16
  • 78
  • 86
0

In addition to missing a spaces in [ i%2 -eq 0 ] and [ "$1" -lt 50 ], your arithmetic use is incorrect along with several variable assignment issues. You need to assign the result of the arithmetic to the value at hand. The form is var=value, not value == var.

You have 3 choices for how to do arithmetic in bash. The old newval=$(expr i / 2) or let newval="$i"/2 or newval=$((i / 2)):

Cleaning up your script, you could do something like:

if [ "$1" -gt 1 ] && [ "$1" -lt 50 ]
then
    for i in $1; do
        //if $i is even
        if (( i % 2 == 0 ))
        then
            newvar=$((i / 2))
            echo "$newvar,"
        //if $i is odd
        else 
            newvar=$((3 * i + 1))
            echo "$newvar,"
        fi
    done
fi

Note, while you can multiply and reassign the loop increment i, it is far better to assign the result to a new variable, e.g. some newvar.

Note how:

newvar=$((i / 2))

and

newvar=$((i * 3 + 1))

It also looks like you intended:

if [ "${#1}" -gt 1 ] && [ "${#1}" -lt 50 ]

to check that the number of characters given is between 1 and 50, otherwise, your for i in $1; do does not make a lot of sense. If you are just testing the value and not the number of characters, get rid of the for altogether:

if [ "$1" -gt 1 -a "$1" -lt 50 ]
then
    val="$1"              ## assign positinal parameter to variable
    ## if $val is even    ## to avoid subtleties in syntax
    if (( val % 2 == 0 ))
    then
        newvar=$((val / 2))
        echo "$newvar,"
    ## if $val is odd
    else 
        newvar=$((3 * val + 1))
        echo "$newvar,"
    fi
fi

Alternative

If I have figured out what you are trying to do, then you might consider doing it this way:

#!/bin/bash

declare -i val
if [ "$1" -gt 1 -a "$1" -lt 50 ]; then
    val="$1"                    # assign val=$1
    while ((val > 0)); do       # loop for val > 0
        if ((val > 1)); then    # set output format
            fmt="%d,"
        else
            fmt="%d"
        fi
        if ((val % 2 == 0))     # if $val is even
        then
            n=$((val / 2))
        else                    # if $val is even
            n=$((val * 3 + 1))
        fi
        printf "$fmt" "$n"
        ((val--))               # decrement val
    done
else
    printf "input value not between 1 and 50\n"
fi

printf "\n"

Example Use/Output

$ bash ql.sh 10
5,28,4,22,3,16,2,10,1,4

Or, rewritten in a more compact form using compound commands:

#!/bin/bash

declare -i val
if [ "$1" -gt 1 -a "$1" -lt 50 ]; then          # validate input 1 - 50
    for ((val = $1; val > 0; val--)); do        # loop for val > 0
        ((val > 1)) && fmt="%d," || fmt="%d"    # print format
        ((val % 2 == 0)) && n=$((val / 2)) || n=$((val * 3 + 1)) # even/odd
        printf "$fmt" "$n"
    done
else
    printf "input value not between 1 and 50\n"
fi

printf "\n"
David C. Rankin
  • 69,681
  • 6
  • 44
  • 72
  • Does `for i in $1` do what the OP expects? I think the OP intended `for i in $(seq 1 $i)` or some such ... Also, do you not get errors on `//` comments? Because I do if I use bash (4.3)... – Martin Tournoij Mar 13 '16 at 23:57
  • No, it doesn't do squat unless he is looping though multiple chars. See the amended answer. – David C. Rankin Mar 13 '16 at 23:58