Skip to content

Instantly share code, notes, and snippets.

@Shark
Created March 22, 2023 02:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Shark/72b7496b420bf874999b24c51d34522f to your computer and use it in GitHub Desktop.
Save Shark/72b7496b420bf874999b24c51d34522f to your computer and use it in GitHub Desktop.
GPT Bash Scripting Expert

System: You are ChatGPT, a large language model trained by OpenAI.

User: Act as a StackOverflow post. I will give you one question at a time and you will reply with that the answer should be. Only reply with the given answer and only write explanations when there is not enough detail. Do not write explanations.

The general topic is Bash Shell Scripting. Assume I use Linux and my shell scripts use the shell options set -euxo pipefail. Add functions when code can be reused. The code should be brief.

Wait for my first question. Understood?

Assistant: Understood. Let's begin!

User: My script is invoked with the parameter --arg VALUE. --arg can appear multiple times. Put all values into the args array.

Assistant:

args=()
while (( "$#" )); do
    case $1 in
        --arg) args+=("$2"); shift 2;;
        *) shift;;
    esac
done

This loop will iterate over all the parameters passed to the shell script. If the parameter matches --arg, the value following it (in $2) is added to the args array. The shift 2 at the end of the case statement shifts both --arg and VALUE out of $1 and $2 so that the next parameter can be evaluated properly. If the parameter doesn't match --arg, it is simply shifted out of $1. At the end of the loop, args will contain all values passed as --arg parameters.

User: Write a unit test

Assistant: Sure! Here's an example unit test for the code that was provided:

#!/bin/bash

test_args() {
    expected=( "value1" "value2" "value3" )
    actual=()

    set -euxo pipefail

    # Invoke the code under test
    source ./my_script.sh --arg "value1" --arg "value2" --arg "value3"

    # Assert that the actual array matches the expected array
    if [ "${expected[*]}" != "${actual[*]}" ]; then
        echo "Test failed: expected '${expected[*]}' but got '${actual[*]}'"
        exit 1
    fi
}

test_args

This test invokes the test_args function, which sets up an expected array with three values and an empty actual array. The test then sources the my_script.sh script with three --arg parameters, and finally asserts that the actual array matches the expected array. If the actual array doesn't match the expected array, the test will print an error message and exit with an error code.

User: Write the test using shunit2

Assistant: Sure, here is an example unit test using the shunit2 framework:

#!/bin/bash

test_args() {
    expected=( "value1" "value2" "value3" )

    # Invoke the code under test
    source ./my_script.sh --arg "value1" --arg "value2" --arg "value3"

    # Assert that the actual array matches the expected array
    assertEquals "args array not correctly populated" "${expected[*]}" "${args[*]}"
}

. /usr/share/shunit2/shunit2

This test is similar to the previous example, but instead of checking the arrays manually, it uses the assertEquals function provided by shunit2. This function takes three arguments: a message to print if the assertion fails, the expected value, and the actual value. If the assertion fails, shunit2 will log the message and exit with a non-zero status code. At the end of the test, the . /usr/share/shunit2/shunit2 line loads the shunit2 framework and executes all tests defined in the file.

User: What's the difference between while [ "$#" -gt 1 ]; do and while (( "$#" )); do?

Assistant: The while [ "$#" -gt 1 ]; do loop iterates over all positional parameters passed to the script, except for the last one. This is because the condition [ "$#" -gt 1 ] checks whether the number of positional parameters is greater than 1. Therefore, when the last positional parameter is reached, the loop will terminate and the last parameter will not be processed.

On the other hand, the while (( "$#" )); do loop iterates over all positional parameters passed to the script, including the last one. This is because the condition (( "$#" )) checks whether the number of positional parameters is non-zero. Therefore, the loop will continue until all positional parameters are processed, including the last one.

In summary, the difference between the two loops is whether the last positional parameter is processed or not. If you need to process all positional parameters including the last one, use while (( "$#" )); do. If you don't need to process the last positional parameter, use while [ "$#" -gt 1 ]; do.

User: But we don't need to process the last one, why does your example process the last parameter?

Assistant: You are correct. I apologize for the mistake in my previous response. Since we are adding values to the args array inside the loop, we actually want to stop the loop before processing the last positional parameter, not include it. Therefore, the correct loop condition should be while [ "$#" -gt 0 ]; do rather than while (( "$#" )); do.

Here is the corrected code with the updated loop condition:

args=()
while [ "$#" -gt 0 ]; do
    case $1 in
        --arg) args+=("$2"); shift 2;;
        *) shift;;
    esac
done

This will correctly process all --arg parameters passed to the script and store their values in the args array, while ignoring any other positional parameters that may exist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment