Script Shell - How to print out echo if two conditions are met? (Only numbers, exakt 2 conditions)

1

0

I'm new to shell scripting and are trying to learn the basics. I'm launching a script using the terminal such as "./scriptgoeshere.sh Argument1 Argument2".

I want the script to print some echo, "Hello World" is probably the best one to use, but I only want it to print out hello world if there are: Exactly two arguments, and those arguments have to consist solely of numbers. For example:

./scriptgoeshere.sh 1523 9643 ./scriptgoeshere.sh 7 96 Should work, however; ./scriptgoeshere.sh 594 arg Should NOT work, this is the script I have at the moment but it doesn't work, Ive tried figuring this out the entire day but cant seem to nail it, I even tried if/elif/else where I had it check if there are 2 inputs in the if part, and if they are both numbers in elif before continuing.

#!/bin/bash
if [ "$#" -eq 2 ] && [ "$@" -eq ^[0-9] ]; 
then
    i=0
    while [ $i -lt 3 ]
    do
        echo "Hello World!"
        sleep 5
        i=$((i+1))
    done
else
echo "Need two arguments and only numbers will be accepted, such as ./scriptgoeshere 153 251"

fi

It blocks the hello world loop until I put in 2 arguments, but it doesnt matter if they are numbers or text, and I get an error then: "./scriptgoeshere.sh: line 2: [: ^[0-9]: integer expression expected". I get that error even if I write ./scriptgoeshere.sh 123 123 in the terminal to launch it. I prefer to not solve it by variables as I want to figure out the arguments when launching the actual script.

This is doing my head in!

This is what I started with to make it check if there are exactly 2 arguments and it works, I just wanna add a small fix to it so those 2 arguments HAS to be numbers. I don't get why it seems to be so hard.

#!/bin/bash
if [ "$#" -eq 2 ];
then
    i=0
    while [ $i -lt 3 ]
    do
        echo "Hello World!"
        sleep 1
        i=$((i+1))
    done
else
echo "Need two argument, only numbers accepted"

fi

Edit: I solved it. I had worked so close.. I just needed to add extra [] to the number part of the if-string. Anyone knows why it needs to be written as [[ "$@" -eq ^[0-9] ]] while the first part can be written with just one [] ?

Trollblod

Posted 2019-10-16T20:42:11.733

Reputation: 27

Sigh… If you keep changing your code I will never finish my answer that tries to elaborate your mistakes, reveal pitfalls and such. – Kamil Maciorowski – 2019-10-17T00:28:05.387

Haha I'm sorry! I've been working through a virtualbox and my keyboard inputs have been fucked and I haven't gotten around to fixing it yet, can't type []$@ for instance which means I've had it all copied over from the VMBox. Noticed a few things went missing and such, I updated it 23mins ago when I managed to solve it, did it previously when the code didnt copy-paste correctly.

I managed to solve it with if [ "$#" -eq 2 ] && [[ "$@" =~ ^[0-9] ]];" – Trollblod – 2019-10-17T00:41:33.923

And you even edited your comment after I read it! :/ And it says your solution is [[ "$@" =~ ^[0-9] ]] but the question body claims it is [[ "$@" -eq ^[0-9] ]]. – Kamil Maciorowski – 2019-10-17T01:40:38.050

Answers

0

At first [ "$#" -eq ^[0-9] ] or [ "$@" -eq ^[0-9] ] (the question was edited while I was composing my answer…). From the context I think you want to test if the parameter is a number.

  • $# is always a number, the number of positional parameters in decimal. You want to test two positional parameters: $1 and $2.
  • [ is a command ([ whatever ] is equivalent to test whatever). It may be a shell builtin but syntactically it's like ls or man. Words that follow (including ]) are parsed by the shell like any words that follow a command: shell expands them (e.g. $# becomes a number), removes quotes; eventually they become parameters to [. One of these expansions is filename expansion where various patterns are matched against existing filenames (filename expansion, globbing). Unquoted ^[0-9] may expand to ^2 ^5 if these files exist. If there is no match or if you disable the feature ^[0-9] will get to [ (as one of its arguments) literally. It looks like a pattern but [ doesn't support pattern matching anyway.
  • -eq requires the previous and the next argument to be integers. $# expands to an integer for sure. There is no way for ^[0-9] to expand to a number in this context.
  • Double-quoted $@ is special, it can expand to multiple words ("fields" in terms of POSIX) despite being quoted. Usually we quote to prevent word splitting: "$foo" expands to one word even if the value contains spaces and such. "$@" expands like "$1" "$2" …, its double-quotes prevent $1 to expand to multiple words (and separately $2 and so on). In this fragment:

    [ "$#" -eq 2 ] && [ "$@" -eq ^[0-9] ]
    

    the second [ is run iff the first succeeds (this is how && works), i.e. iff there are exactly two positional parameters. In such case "$@" is equivalent to "$1" "$2" and the second test becomes

    [ "$1" "$2" -eq ^[0-9] ]
    

    This doesn't make sense. [ may complain in different ways depending on actual values of parameters and expansion of ^[0-9], still it's garbage.


Now about [[ "$#" = [0-9] ]].

Note: this snippet was in the first revision of the question. The question was edited while I was composing my answer. I decided to keep this fragment because [[ may be useful eventually anyway.

  • Again: $# is always a number.
  • [[ is not equivalent to [. Any sane test you can do with [ you can rewrite with [[ (although you should't do this mindlessly by jut replacing [ with [[). There are additional tests [[ can do, it is more powerful. While [ (and test) is required by POSIX, [[ is not.

    Important thing: [[ is not a command like ls or [. It's a keyword, it changes the way the shell parses words that follow. Everything between [[ and ]] is parsed differently.

    If the operator is =, the next argument is a pattern. Some characters/syntaxes (e.g. * or [0-9]), when unquoted, are matched against the argument before =. It's like filename expansion, but not against existing filenames. Your code tested if (expanded) $# is exactly one digit. The whole test was

    [[ "$#" -eq 2 ]] && [[ "$#" = [0-9] ]]
    

    foo && bar runs bar iff foo succeeds. The second [[ was tested only if $# expanded to 2, so it had to succeed because 2 is exactly one digit. In this case the second [[ changes nothing.


Let's say you realized your mistake and tested if $1 is a number.

  • Like above [[ "$1" = [0-9] ]] returns success if $1 expands to exactly one digit.
  • [[ "$1" = [0-9][0-9] ]] – exactly two digits.
  • [[ "$1" = [0-9]* ]] – something that starts with a digit.

To detect any number of digits you need =~. This operator inside [[ ]] is somewhat similar to =, but now the pattern is an extended regular expression. It doesn't need to match the whole string to the left of the operator though, a substring is enough (therefore ^ and $ anchors are needed in your case):

  • [[ "$1" =~ [0-9] ]] returns success if $1 expands to a string containing a digit.
  • [[ "$1" =~ [0-9][0-9] ]] – … containing two digits next to each other.
  • [[ "$1" =~ ^[0-9]$ ]] – a string that is exactly one digit.
  • [[ "$1" =~ ^[0-9]*$ ]] – zero or more digits.
  • [[ "$1" =~ ^[0-9][0-9]*$ ]] – one or more digits.
  • [[ "$1" =~ ^[0-9]+$ ]] – one or more digits.

[[ "$1" =~ ^[0-9]+$ ]] and [[ "$2" =~ ^[0-9]+$ ]] are the tests you may want. To allow leading - modify them like this: [[ "$1" =~ ^-?[0-9]+$ ]]. The whole testing fragment may be

[ "$#" -eq 2 ] && `[[ "$1" =~ ^-?[0-9]+$ ]]` && `[[ "$2" =~ ^-?[0-9]+$ ]]`

Inside [[ ]] variables/parameters may not be double-quoted and their expanded values will not undergo word splitting and globbing. So you can write [[ $1 =~ ^-?[0-9]+$ ]] and it's safe. It's an exception; in general you should double-quote variables. Here double-quoting disturbs nothing, so I advise to quote anyway to get used to this good general practice.


You claim you solved the problem using [[ "$@" -eq ^[0-9] ]]. Sorry, I don't believe you. If there are exactly two positional parameters and they are numbers, I don't see a way this can be syntactically correct, let alone test what you want. In other cases… I still don't see a way.

… using [[ "$@" =~ ^[0-9] ]] (from your comment, incoherent with the question). This is not a proper solution. My tests indicate that "$@" inside [[ ]] behaves like unquoted $@ treated as one word without word splitting and globbing. This is very similar to how regular variables behave inside [[ ]], although it was not obvious to me in advance, because I know "$@" is not like regular variables.

Side note: for this reason I have never wanted to use "$@" inside [[ ]]. I cannot imagine a scenario when it's the right thing.

When there are exactly two positional parameters, your "solution" becomes [[ "$1 $2" =~ ^[0-9] ]]. It's syntactically valid and it checks if the very first character of the first argument to your script is a digit. Only this. Run ./scriptgoeshere.sh 0h well, the arguments will pass the test.


Notes:

  • [0-9] (as globbing pattern or regular expression) depends on your current locale. Most likely it will work as you expect, but technically it's possible to have a locale where 9 precedes 0 or where 2 is beyond 0-9. The most general way to match against a digit is [[:digit:]]. See this.

    On the other hand you may want to use $1 and/or $2 with shell arithmetic ($((…))), [ "$1" -ge 50 ] or such. Then you need Arabic numerals only. Even if [[:digit:]] includes them in your locale (it probably does), it may include other digits as well. Consider [0123456789]; it is explicit and not locale-dependent.

  • Not every string that matches ^-?[0-9]+$ is considered integer from the point of view of [ and/or [[ (e.g. when you do [ "$1" -eq 0 ]). Each tool has a range of integers it can handle. Similarly shell arithmetic may produce mathematically wrong results if you feed it with a string of digits that describes an out-of-range integer.

  • Mathematically 1.00 is an integer. Some tools may accept it as integer, some may not. What about 1e100?

Kamil Maciorowski

Posted 2019-10-16T20:42:11.733

Reputation: 38 429