0

I want to write a small Bash function that I can pass a command as string and a search needle and if the execution output of the command contains the given search needle, it should print some "OKAY", else "ERROR".

That's what I came up with so far:

red=`tput setaf 1`
green=`tput setaf 2`
reset=`tput sgr0`

function psa_test {
  result=$($1 2>&1)
  echo "Result: $result"
  if [[ $result == *"$2"* ]]; then
    echo "[${green} OK  ${reset}] $3"
  else
    echo "[${red}ERROR${reset}] $3"
  fi
  echo "        Command: '$1' | Needle: '$2' | Name: '$3'"  
}

If I invoke it like that

psa_test 'curl -v google.com' "HTTP 1.1" "Testing google.com"

it works beautifully:

# .. output
[ OK  ] Testing google.com
            Command: 'curl -v google.com' | Needle: 'HTTP 1.1' | Name: 'Testing google.com'

But if I have some embedded string in my command, it doesn't work anymore, e.g.:

psa_test 'curl --noproxy "*" http://www1.in.example.com' "HALLO 1" "HTTP www1.in.example.com"

Output:

# Proxy answers with HTML error document ... <html></html> ... saying
# that it can't resolve the domain (which is correct as 
# it is an internal domain). But anyway, the --noproxy "*" option 
# should omit all proxies!
[ERROR] HTTP www1.in.example.com
        Command: 'curl --noproxy "*" http://www1.in.example.com' | Needle: 'HALLO 1' | Name: 'HTTP www1.in.example.com'

Note that if I execute

curl --noproxy "*" http://www1.in.example.com

in my shell does ignore proxies (which we want), while

curl --noproxy * http://www1.in.example.com

does not.

I got some similar behaviour if I try to test a MySQL database with mysql -u ... -p ... -e "SELECT true" . So I guess it has something to do with the quotes. Would be glad if someone can give me a hint to improve this function. Thanks!

vitus37
  • 118
  • 6
  • The `*` will be expanded by the shell to all the filenames in the current directory (except those prefixed by a `.`). That expansion does not happen if the `*` is inside quotes (single or double). – cdarke Mar 01 '17 at 10:09
  • Thanks cdarke. I am aware of the need for the quotes but in my test case I do use them. I also tried to flip " and ' and some escaping variants, but non does solve my problem. Is it possible that I need some sort of `eval` instead of `$(...)`? – vitus37 Mar 01 '17 at 10:35
  • 2
    Putting the command as the first parameter pretty much wrecks the whole concept because in order for this to be useful, you will want to be able to pass in commands with arbitrarily complex quoting. Put the strings as `$1` and `$2`, shift them off, and use `"$@"` as the command. – tripleee Mar 01 '17 at 10:37
  • 1
    Looks like you are trying to reinvent https://testanything.org/producers.html#shell poorly anyway. – tripleee Mar 01 '17 at 10:38
  • 1
    If you want to write shell scripts, you want to understand at least the basics of quoting. The bare unquoted `$($1 2>&1)` is a big red flag. Read http://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-variable – tripleee Mar 01 '17 at 10:40
  • 1
    Please don't do this. At the very least, if you want to try writing color codes to your output, you should check if the output is to a tty and disable the color codes if it is not. And allow an environment variable to override the colors and disable them. The number of logfiles that are completely unreadable because they are cluttered with obfuscating terminal codes is already too high. – William Pursell Mar 01 '17 at 11:00
  • Hi @triplee, thanks for your answers! As you can see my experience with Bash is sparse and I'm still learning the treats. The shifting bumped me into the right direction, thanks! And indeed, I must have tried to invent sth, but I hadn't found the right thing yet. – vitus37 Mar 01 '17 at 11:04

1 Answers1

2

Trying to provide entire commands as a single string requires quoting in all kinds of inconvenient ways. I would suggest you try a slightly different approach that does not require that.

#!/bin/bash
red=`tput setaf 1`
green=`tput setaf 2`
reset=`tput sgr0`

psa_test()
{
  local needle="$1"
  local name="$2"
  local -a command=("${@:3}")
  local result ; result=$("${command[@]}" 2>&1)
  echo "Result: $result"
  if
    [[ $result == *$needle* ]]
  then
    echo "[$green OK  $reset] $name"
  else
    echo "[$red ERROR $reset] $name"
  fi
  echo "        Command: ${command[@]} | Needle: $needle | Name: $name"
}

Then you can call your function by passing your command as an unquoted string, exactly like you would call it in an interactive shell, but putting the first two arguments before.

psa_test "HTTP/1.1" "Testing google.com" curl -v google.com

Some notes :

  • The command is stored in an array, and executed by expanding all its elements, individually quoted to prevent word splitting (this is what "${command[@]}" does).

  • The double quotes around the array expansion are very important, because they tell the shell to expand the array one word per array element, and prevent word splitting from occurring inside each array element (to allow for array elements containing spaces, for instance).

  • You should declare your variables local inside functions. It is not strictly necessary, but it will make scripts easier to debug and less prone to nasty failure modes when you have several functions with variables of the same name.

  • I think "HTTP/1.1" is the search string you want

  • If performance is a concern, you should try different approaches to text matching (i.e. Bash regexes =~, grep) to see which is best for your data.

Please note that the script above could be simplified by :

  • Replacing local -a command=("${@:3}") by shift 2
  • Replacing "${command[@]}" by "$@" everywhere it is used

In this case it would make sense, but I used this opportunity to illustrate how building commands as arrays works, as it is often useful in scripts, but in simple cases using positional arguments instead works very well and improves readability.

Fred
  • 6,590
  • 9
  • 20
  • There's no need for an array really; just `shift 2` and use `"$@"` directly. – tripleee Mar 01 '17 at 10:47
  • @tripleee Of course one could do away with shifting the first two arguments in this case (it would simplify the script), but knowing how to use arrays to build commands is very useful (sometimes "$@" is not an option because positional parameters are already used for something else), and I often try to offer that as a solution to help spread the word. – Fred Mar 01 '17 at 10:51
  • Hi @Fred , thanks for your detailed answer! That's exactly what the problem was about. Still have to get comfortable with Bash's peculiarities. – vitus37 Mar 01 '17 at 11:08
  • 1
    @vitus37 Thanks. Learning how to use Bash takes some time, I still learn something new almost every day, and you have to build a toolkit of practices you know to be working well in terms of readability, performance and security. SO is very helpful for that. – Fred Mar 01 '17 at 11:19