2

I am new with scripting and I want to write a script that has to process it's argument(s) as following:

$ tandenstokers "| | x|| | |+|"
|| x |||| + | = 9 (11 tandenstokers)

$ tandenstokers \| \| x\|\| \| \|\+\|
|| x |||| + | = 9 (11 tandenstokers)

Where as I get this as output:

$ tandenstokers \| \| x\|\| \| \|\+\|
|| x |||| + | = 0 (0 tandenstokers)

What it does is basically count the amount of 'sticks' and apply simple maths to it. The resulting output is: 1. The given argument(s) with only spaces left before and after an x or + 2. 9 in the example above is the result of the maths. 3. 11 is the amount of sticks used in the formula (a + and a x count as two sticks)

So far I have the following code, and I don't understand where my mistake is:

#!/bin/bash

uitdrukking="$*"
size=${#uitdrukking}
mooi=$(echo "$uitdrukking" | sed -e 's/ //g' | sed -e 's/+/ + /g' | sed -e 's/x/ x /g')
x=0
y=1
a=1
n=0
m=0
sub=0
while [ $a -le $size ]
do
((a++))
    if [ ${uitdrukking:$x:$y} -eq "|" ]
        then
            ((n++))
            ((x++))
            ((y++))
            ((m++))
    elif [ ${uitdrukking:$x:$y} -eq "+" ]
        then
            ((x++))
            ((y++))
            m=$[ $m + 2 ]
    elif [ ${uitdrukking:$x:$y} -eq "x" ]
        then
        ((x++))
        ((y++))
        m=$[ $m + 2 ]
            while [ ${uitdrukking:$x:$y} -eq "|" ]
            do
                ((sub++))
                ((x++))
                ((y++))
                ((m++))
            done
            n=$[ $n * $sub ]
    else
        ((x++))
        ((y++))
    fi
    done
echo "$mooi = $n ($m tandenstokers)"

Solved this problem by starting from scratch with a different approach, which resulted in the following correct code:

#!/bin/bash
echo "$(echo "$*" | sed -e 's/ //g' | sed -e 's/+/ + /g' | sed -e 's/x/ x /g') = $(($(echo "$*" | sed -e 's/ //g' | sed -e 's/\(\|[^x+]*\)/(\1)/g' | sed -e 's/|/1 + /g' | sed -e 's/ + $//g' | sed -e 's/ + +/ + /g' | sed -e 's/ + x/ \* /g' | sed -e 's/ + \([^1]\)/\1/g' | sed -e 's/x/ \* /g'))) ($(($(echo "$*" | sed -e 's/ //g' | sed -e 's/|/1 p /g' | sed -e 's/ p $//g' | sed -e 's/x/2 p /g' | sed -e 's/+/2 p /g' | sed -e 's/p/+/g'))) tandenstokers)"
4
  • 2
    What mistake? When asking about this sort of issue, you need to tell us what you expect to see (as you have done), but also what you actually see. How exactly is this failing? Don't expect people to run random code on their machines only to see how it fails. Also, it might not fail in the same way in different environments.
    – terdon
    Dec 6, 2016 at 21:19
  • Edited and added my output. Environment is the bash shell as the title might suggest.
    – noone
    Dec 6, 2016 at 21:27
  • Thanks. And yes, I noticed it was bash but my point was a general one. You should always mention how something fails and not just that it fails. I get various error messages when running this, for example. All of which point to what your issue is but none of which are mentioned in your question.
    – terdon
    Dec 6, 2016 at 21:29
  • By the way, if your solution works for you, please post it as an answer and accept it so the question can be marked as answered.
    – terdon
    Dec 7, 2016 at 17:38

3 Answers 3

4

I'm afraid your script is not very easy to understand. Your approach seems overly complicated for what it is trying to do and since you're not explaining what you think each part of your script does, it is not trivial to figure it out.

That said, your first issue (which would have been obvious had you included the error messages) is that you're using -eq for lexical comparison. You need = instead since you're matching strings, not numbers.

The next issue, which again would be obvious from the error messages is

/home/terdon/scripts/foo.sh: line 15: [: too many arguments
/home/terdon/scripts/foo.sh: line 21: [: too many arguments
/home/terdon/scripts/foo.sh: line 26: [: too many arguments

This is because the various ${uitdrukking:$x:$y} expand to strings containing spaces, not to a single character. This is probably because you are incrementing your various counter variables in many places so your $y will be >1 very quickly. I think (but, again, I can' be sure since your question doesn't actually explain what you think is going on) that you've misunderstood how the ${var:x:y} syntax works. It doesn't extract the substring of var from position x to position y. It extracts the substring of var starting from position x and y characters long.

The first rule to programming in any language is: when something goes wrong, print out all the variables. 9 times out of 10, the problem is that a variable doesn't have the value you think it has.

In any case, you have too many issues to debug here and it would be much simpler to require from scratch. For example, this script does what you want:

#!/bin/bash
mooi=$(echo "$*" | sed -e 's/ //g' | sed -e 's/+/ + /g' | sed -e 's/x/ x /g')
## I use fold to print one character at a time and then iterate
## over the resulting strings. 
while read char; do
    case $char in
        ## If this is a |, increment the total number of |
        ## found and the current number (until the next operator).
        "|")
            ((pipeNum++))
            ((totPipes++))
            ;;
        ## If this is an operator
        [+x])
            ## Change x to * for bc
            char=$(echo "$char" | tr 'x' '*')
            ## Increment the operator count by 2 as requested. 
            operators=$((operators + 2))
            ## Append the number of pipes so far and the 
            ## current operator to the $string variable. This 
            ## will hold the expression we'll give to bc. 
            string="$string $pipeNum $char"
            ## reset the pipeNum to 0 for the next operation. 
            pipeNum=0
            ;;
            ## Ignore all other cases. 
            "*")
                continue
                ;;
    esac
done < <(fold -w 1 <<<"$*")
## Add the last set. 
string="$string $pipeNum"
## Count the total
tandenstokers=$((totPipes + operators))
## Use bc to calculate
echo "$mooi = $(echo "$string" | bc) ($tandenstokers tandenstokers)"

To see it in action:

$ foo.sh '| | x | | | | + |'
|| x |||| + | = 9 (11 tandenstokers)
$ foo.sh \| \| x\|\| \| \|\+\|
|| x |||| + | = 9 (11 tandenstokers)
2
  • Thanks for your help, I've noticed this in the code: < <(fold -w 1 <<<"$*"), what exactly does that do?
    – noone
    Dec 7, 2016 at 17:28
  • @noone the fold command "folds" its input. In this case, because of the -w 1, it prints one character per line. If you used w -12, it would print 12 per line and so on.
    – terdon
    Dec 7, 2016 at 17:37
1

First of all, to answer your question, -eq is for integer comparison. Use = for strings. I assume this is your main mistake.

Some advice:

  • quote your variables by default (i.e. "$n", not just $n). Unquoting them may be necessary in some case but it must be an exception
  • always use set -u to detect the use of uninitialized variables
  • concatenate your sed instructions in one sed command: sed -e 's/ //g; s/[+x]/ & /g;' instead of the three-command pipeline sed -e 's/ //g' | sed -e 's/+/ + /g' | sed -e 's/x/ x /g'
  • think shell (I know this is difficult when you are learning sh): divide your code into small commands (functions), use parameters and take advantage of the shell parser (for word in "$@"). Here, what I see is a big loop with a lot of N++ and you are parsing everything yourself, character by character.

For the fun of code golf, here is the calculation part in one sed + arithmetic evaluation:

tandenstokers()
{
    echo "$((
        $(sed -e '
            s/ //g
            s/[xX]/*/g
            s/|\+/\(&\)/g
            s/|/+1/g
        ' <<< "$*" )
    ))"
}

$ tandenstokers "| | x|| | |+|"
9

$ tandenstokers "|| + ||| - | X |||| / ||"
3

This is a radically different solution. Take it as a curiosity, and don't drop yours! What I do is just turning your tandenstokers formula in a regular arithmetic formula, which I finally evaluate with $(( ... )).

I leave you as an exercice the count of toothpicks in the formula.

2
  • Thanks for your awnser, but what does ' <<< "$*" mean?
    – noone
    Dec 7, 2016 at 17:20
  • @noone The command before <<< takes its standard input from the string after <<<. It's the same as echo "$*" | sed -e '...' without resorting to a command (echo) and a subshell (due to the pipe).
    – xhienne
    Dec 7, 2016 at 17:29
0

This is my working solution for the problem:

#!/bin/bash
echo "$(echo "$*" | sed -e 's/ //g' | sed -e 's/+/ + /g' | sed -e 's/x/ x /g') = $(($(echo "$*" | sed -e 's/ //g' | sed -e 's/\(\|[^x+]*\)/(\1)/g' | sed -e 's/|/1 + /g' | sed -e 's/ + $//g' | sed -e 's/ + +/ + /g' | sed -e 's/ + x/ \* /g' | sed -e 's/ + \([^1]\)/\1/g' | sed -e 's/x/ \* /g'))) ($(($(echo "$*" | sed -e 's/ //g' | sed -e 's/|/1 p /g' | sed -e 's/ p $//g' | sed -e 's/x/2 p /g' | sed -e 's/+/2 p /g' | sed -e 's/p/+/g'))) tandenstokers)"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .