如何使用包含的函数而不是脚本来解析符合shell检查的参数?

p5cysglq  于 2023-04-12  发布在  Shell
关注(0)|答案(1)|浏览(97)

背景

我正在尝试使用单独的文件和函数编写一个像样的bash解析器:

  • 参数解析(例如,确定要做什么)
  • 解析参数处理(例如,执行这些操作)
  • 打印CLI用法。

然而,当我试图将“POSITIONAL_ARGS”传递到parse_args() {时,参数似乎没有到达。

示例:

假设我在目录中有以下main.sh文件:/src

#!/bin/bash

POSITIONAL_ARGS=()
# source arg_parser.sh
source src/arg_parser/arg_parser.sh
source src/arg_parser/print_usage.sh

# print the usage if no arguments are given
[ $# -eq 0 ] && { print_usage; exit 1; }
echo "POSITIONAL_ARGS=$POSITIONAL_ARGS"
parse_args "$POSITIONAL_ARGS"

下面的arg_parser.sh文件:src/arg_parser/arg_parser.sh

#!/bin/bash

parse_args() {
  local positional_args="$1"

  # Specify default argument values.
  local apply_certs_flag='false'
  local check_http_flag='false'
  local check_https_flag='false'
  local generate_certs_flag='false'
  local project_name_flag='false'
  local port_flag='false'

  while [[ $# -gt 0 ]]; do
    echo "dollar1=$1"
    echo "positional_args=$positional_args"
    echo "dollar hashtag=$#"
    case $1 in
      -a|--apply-certs)
        apply_certs_flag='true'
        shift # past argument
        ;;
      -ch|--check-http)
        check_http_flag='true'
        shift # past argument
        ;;
      -cs|--check-https)
        check_https_flag='true'
        shift # past argument
        ;;
      -g|--generate-certs)
        generate_certs_flag='true'
        shift # past argument
        ;;
      -n|--project-name)
        project_name_flag='true'
        project_name="$2"
        assert_is_non_empty_string "${project_name}"
        shift # past argument
        shift
        ;;
      -p|--port)
        port_flag='true'
        port="$2"
        assert_is_non_empty_string "${project_name}"
        shift # past argument
        shift
        ;;
      -*)
        echo "Unknown option $1"
        print_usage
        exit 1
        ;;
    esac
  done

  set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
}

预期输出

当我运行./src/main.sh -a时,我会期望输出:

echo "POSITIONAL_ARGS=-a
dollar1=-a
positional_args=-a
dollar_hashtag=1

dollar1=-a
positional_args=-a
dollar_hashtag=0

输出

然而,实际的输出是一个无限循环,而不是解析/吃/转移参数:

dollar1=
positional_args=
dollar hashtag=1
dollar1=
positional_args=
dollar hashtag=1
dollar1=
positional_args=
dollar hashtag=1
.... etc.

提问

如何将CLI中的参数从src/main.sh传递到文件src/arg_parser/arg_parser.sh中的parse_args()函数中,以便可以解析它们?

注意事项

我以前有

-p|--prereq)
      prerequistes_only_flag='true'
      shift # past argument
      ;;
    -*|--*)
      echo "Unknown option $1"
      print_usage
      exit 1
      ;;
    *)
      POSITIONAL_ARGS+=("$1") # save positional arg
      shift # past argument
      ;;
  esac

然而,shellcheck说--*)将永远不会到达,因为-*|

假设

我认为至少有一个错误可能是我在最初的问题中将POSITIONAL_ARGS视为字符串而不是数组。然而,shellcheck告诉我它是一个数组:

In src/main.sh line 26:
echo "POSITIONAL_ARGS=$POSITIONAL_ARGS"
                      ^--------------^ SC2128 (warning): Expanding an array without an index only gives the first element.
oxosxuxt

oxosxuxt1#

问题

  • 我认为POSITIONAL_ARGS包含了位置参数。
  • 我把POSITIONAL_ARGS当作变量而不是数组。

缺失信息:

  • $@将传入的参数作为数组给出。
  • $#给出了传入参数的长度/数量。

解决方案

找到了以下src/main.sh含量的解决方案:

#!/bin/bash

source src/arg_parser/arg_parser.sh
source src/arg_parser/process_args.sh
source src/arg_parser/print_usage.sh

# print the usage if no arguments are given
[ $# -eq 0 ] && { print_usage; exit 1; }

parse_args "$@"

say_hello() {
  echo "Done parsing args. Hello world."
}
say_hello

以及附带的src/arg_parser/arg_parser.sh文件内容:

#!/bin/bash

parse_args() {
  # local positional_args=("$@")

  # Specify default argument values.
  local apply_certs_flag='false'
  local check_http_flag='false'
  local check_https_flag='false'
  local generate_certs_flag='false'
  local project_name_flag='false'
  local port_flag='false'

  while [[ $# -gt 0 ]]; do
  # while [[ "${#positional_args[@]}" -gt 0 ]]; do
    case $1 in
      -a|--apply-certs)
        apply_certs_flag='true'
        shift # past argument
        ;;
      -ch|--check-http)
        check_http_flag='true'
        shift # past argument
        ;;
      -cs|--check-https)
        check_https_flag='true'
        shift # past argument
        ;;
      -g|--generate-certs)
        generate_certs_flag='true'
        shift # past argument
        ;;
      -n|--project-name)
        project_name_flag='true'
        project_name="$2"
        assert_is_non_empty_string "${project_name}"
        shift # past argument
        shift
        ;;
      -p|--port)
        port_flag='true'
        port="$2"
        assert_is_non_empty_string "${project_name}"
        shift # past argument
        shift
        ;;
      -*)
        echo "Unknown option $1"
        print_usage
        exit 1
        ;;
    esac
  done

  echo "apply_certs_flag=$apply_certs_flag"

}

改进空间

可以看到,我删除了positional_args的使用,而是使用了隐式数组$@和数组长度$#。我认为这个解决方案可以通过将$@positional_args交换来改进,以使其更具可读性。此交换中的一个问题是shift命令会吃掉传入数组$@中的第一个元素,而不是local positional_args数组中的第一个元素。
因此,要解决这个问题,可以更改shift参数以吃掉local positional_args数组中的第一个元素。

相关问题