0

I have my own implementation of a Redis server which I'd like to test through a shell script.

The general idea is to feed it with some commands through nc, and since nc prints the output of my program (response) to stdout, to capture the response in a script variable and compare it with the expected output.

I haven't been able to get it to work and I don't know what the issue is and how to solve it.

This is a part of the script:

#!/bin/bash

set -eu

PORT=6380;

RUST_LOG=info ./run.sh --port $PORT 2>/dev/null & sleep 1; # Give it some time to start.

i=0;

i=$((i+1)); printf 'Running test #%d...' "$i";
response=$(printf "*1\r\n\$4\r\nPING\r\n" | nc localhost $PORT);
if [ "$response" = '+PONG\r\n' ]; then
  printf ' PASSED'
else
  printf ' FAILED\nGot:\n%s\n\n' "$response"
fi;
sleep 0.1;

pkill redis-server;

That's just one example of a test. When a user sends the PING command, the expected response is PONG, but it is encoded and sent back as +PONG\r\n.

The CLI output of this test run ($ ./test.sh) is:

Running test #1... FAILED
Got:
+PONG

So, it seems that the variable $response indeed contains what it should.

I tried removing +, \r, \n, \r\n from the reference output to no avail, just to see if that would help.

I'll have more complicated tests that include a character that needs to be escaped ($), such as \$5\r\nHello, in the response.

By the way, manually setting response works as expected.

response='+PONG\r\n';
if [ "$response" = '+PONG\r\n' ]; then
  echo Equal
else
  echo Different
fi;

This prints Equal, as expected.

After adding debug output through set -ex, I can see that $response stores +PONG\r, i.e., without \n.

Running test #1...++ printf '*1\r\n$4\r\nPING\r\n'
++ nc localhost 6380
+ response=$'+PONG\r'
+ '[' $'+PONG\r' = '+PONG\r\n' ']'
+ printf ' FAILED\nGot:\n%s\n\n' $'+PONG\r'
 FAILED
Got:
+PONG

Okay, so then setting the reference output to +PONG\r should work, right?

Running test #1...++ printf '*1\r\n$4\r\nPING\r\n'
++ nc localhost 6380
+ response=$'+PONG\r'
+ '[' $'+PONG\r' = '+PONG\r' ']'
+ printf ' FAILED\nGot:\n%s\n\n' $'+PONG\r'
 FAILED
Got:
+PONG

Clearly not.

I tried wrapping response in {}, like this: if [ "${response}" = '+PONG\r' ]; then, to no avail.

What am I missing?

I'm also curious to know how and why \n got lost.

I'm running it from zsh on a mac if that should be noted, but the shebang clearly tells it to use bash.

If I execute ps -p $$ from the test script, I get 51547 ttys003 0:00.02 /bin/bash ./test.sh.

21
  • You said that the expected response is encoded (e.g., +PONG\r\n), but then say +PONG (as output after running test.sh) is correct. Shouldn't a working version print out +PONG\r\n instead? Why do you think otherwise?
    – Stanley Yu
    Commented Apr 17 at 16:31
  • 1
    consider adding typeset -p response to see what's actually stored in the variable
    – markp-fuso
    Commented Apr 17 at 17:04
  • 2
    when you say \r\n are you actually looking for the 4 literal characters '\', \r', '\' and 'n', or are you looking for the 2 characters \r (carriage return) and \n (linefeed)? if the latter then consider [ "$response" = $'+PONG\r\n' ]
    – markp-fuso
    Commented Apr 17 at 17:07
  • 2
    response=$'+PONG\r' => the $ prefix says to treat \r as the single carriage return character; without the $ prefix you end up dealing with 2 separate characters '\' and 'r'; consider updating your code to match what's shown in the typeset -p output, namely: [ "$response" = $'+PONG\r' ]
    – markp-fuso
    Commented Apr 17 at 17:15
  • 2
    For sh compliance you can adopt the recommendations in SC3003 -- in other words, allow printf to convert escape sequences to the intended character before assigning to a variable. The newline character got lost because shell command substitution trims trailing newlines. See: pubs.opengroup.org/onlinepubs/9699919799/utilities/…
    – Stanley Yu
    Commented Apr 17 at 17:30

1 Answer 1

4

Your comparison test is not working as expected because of two things:

  1. The actual response from the server (as I understand from your original post and comments) is the string +PONG followed by a carriage return (aka CR, ^M, \r) followed by a newline (aka linefeed, LF, NL, ^J, \n). However, your test tried to compare it to the literal characters \r\n. In other words, you tried to compare characters with their backslash-escaped equivalents.

  2. The trailing newline character as captured in your response variable is stripped due to command substitution behavior.

To fix your test, you can:

  1. Ensure the comparison is between the right characters. In other words, adopt @markp-fuso's syntax of $'+PONG\r\n' for your reference value.

  2. Ensure your newline is not stripped. One such solution is to append a throwaway value at the end of your desired value and then remove it after command substitution finishes. Some extra trickery is required in order to also preserve exit status. It is described in detail here.

Taken together, here is an example solution (without the exit code trickery):

response=$(printf '*1\r\n$4\r\nPING\r\n' | nc localhost "$PORT"; echo .)
response=${response%.}
if [ "$response" = $'+PONG\r\n' ]; then
  printf ' PASSED'
else
  printf ' FAILED\nGot:\n%s\n\n' "$response"
fi

Note that the $'...' syntax, initially from ksh93, whilst now standard for sh since the 2024 edition of the POSIX specification is still not supported by a few sh implementations, notably dash, the sh of Debian and derivatives, so you may want to change your shebang to #! /usr/bin/env zsh or #! /usr/bin/env bash for instance (ksh would not work on systems where ksh is still ksh88 or pdksh) if you want your script to be portable to other systems.

Also note that you don't need to escape the $ with \ if you use strong quotes ('...') in place of "..." inside which parameter expansion is still performed. You could also use $'...' (inside which there is no parameter expansions either) in which case the \r, \n would be converted to the CR and LF control characters by that $'...' operator before being passed to printf instead of printf converting them itself.

Using printf '%s\r\n' '*1' '$4' PING would also make it clearer that you want to output three CRLF delimited lines.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.