【问题标题】:Parsing optional and not optional arguments解析可选和非可选参数
【发布时间】:2022-07-24 17:58:24
【问题描述】:

我是 bash 的新手,在阅读并尝试了很多关于如何解析参数的内容之后,我无法实现我真正想做的事情,我想解析可选参数而不是可选参数。更具体地说,我想解析 3 个参数,第一个(fastaq 文件)第二个(第二个可选的 fastaq 文件)第三个参数,它将是一个目录。

my_script.sh -f1 file1.fasta --f2 file2.fasta -d/home/folder1/folder2

my_script.sh -f1 file1.fasta -d /home/folder1/folder2

我尝试了很多方法,但我不知道如何让程序识别何时有两个 fasta 文件和一个目录,以及何时只有一个 fasta 文件和一个目录。

有了这个参数,我想将它们保存在变量中,因为它们稍后会被第三方使用。

我试过这个:



for i in "$@"; do
 case $i in
   -f1=|-fasta1=)
     FASTA1="${i#=}"
     shift # past argument=value
     ;;
   -d) DIRECTORY=$2
  shift 2
     ;;
   -d=|-directory=) DIRECTORY="${i#=}"
   shift # past argument=value
     ;;
   --f2=|-fasta2=) FASTA2="${i#*=}"
    shift # past argument=value
     ;;
   *)
     ;;
 esac
done

但我刚刚得到这个

scripts_my_first_NGS]$ ./run.sh -f1 fasta.fasta -d /home/folder1
FASTA1  =
DIRECTORY     =
FASTA2     =

【问题讨论】:

    标签: bash parsing script


    【解决方案1】:

    基本上,您需要为不与等号一起使用的选项版本添加单独的解析器。

    您的shift 命令也没有用,因为您正在处理for 循环。因此,请将其转换为 while [[ $# -gt 0 ]]; do 循环。

    我还添加了一些我建议添加的修改。

    while [[ $# -gt 0 ]]; do
        case $1 in
        -f1|-fasta1)
            FASTA1=$2
            shift
            ;;
        -f1=*|-fasta1=*)
            FASTA1=${1#*=}
            ;;
        -d|-directory)
            DIRECTORY=$2
            shift
            ;;
        -d=*|-directory=*)
            DIRECTORY=${1#*=}
            ;;
        -f2|fasta2)
            FASTA2=$2
            shift
            ;;
        -f2=*|-fasta2=*)
            FASTA2=${1#*=}
            ;;
        -*)
            echo "Invalid option: $1" >&2
            exit 1
            ;;
        --)
            # Do FILES+=("${@:2}") maybe
            break
            ;;
        *)
            # TODO
            # Do FILES+=("$1") maybe
            ;;
        esac
    
        shift
    done
    

    选项的等于和不等于版本的“解析器”也可以通过以下方式统一 使用辅助函数:

    function get_opt_arg {
        if [[ $1 == *=* ]]; then
            __=${1#*=}
            return 1
        elif [[ ${2+.} ]]; then
            __=$2
            return 0 # Tells that shift is needed
        else
            echo "No argument provided to option '$1'." >&2
            exit 1
        fi
    }
    
    while [[ $# -gt 0 ]]; do
        case $1 in
        -d|-directory|-d=*|-directory=*)
            get_opt_arg "$@" && shift
            DIRECTORY=$__
            ;;
        -f1|-fasta1|-f1=*|-fasta1=*)
            get_opt_arg "$@" && shift
            FASTA1=$__
            ;;
        -f2|fasta2|-f2=*|-fasta2=*)
            get_opt_arg "$@" && shift
            FASTA2=$__
            ;;
        -*)
            echo "Invalid option: $1" >&2
            exit 1
            ;;
        --)
            # Do FILES+=("${@:2}") maybe
            break
            ;;
        *)
            # TODO
            # Do FILES+=("$1") maybe
            ;;
        esac
    
        shift
    done
    

    更新

    我找到了一个不依赖 getopt[s] 的命令行解析的完整解决方案,而且它做得更加一致:https://konsolebox.io/blog/2022/05/14/general-command-line-parsing-solution-without-using-getopt-s.html

    【讨论】:

    • 这工作谢谢。我有几个问题。为什么目录是“两次”。为什么有些人有 = 和其他人 =*。最后你想对最后一个 cmets 表示什么 TODO Do FILES+=("$1") 可能
    • 最后几部分用于默认参数,而不是选项。大多数情况下,您将它与文件参数一起使用。 “为什么两次?”因为-d /home/folder1/folder2-d=/home/folder1/folder2 是不同的形式。前者分配两个位置参数空间,而后者只分配一个并且将选项参数包含在选项中。
    【解决方案2】:

    永远不要自己解析命令行选项!

    如果您不需要 GNU 样式的长选项,请改用 Bash 函数 getopts,否则使用 GNU 程序 getopt

    以下示例使用FASTA 的数组。 FASTA1${FASTA[0]}FASTA2${FASTA[1]}。如果是getopts,则可以多次使用一个选项字符(-f)。

    使用getopts,只有一个字符选项:

    #! /bin/bash
    
    FASTA=()
    DIRECTORY=
    
    while getopts 'f:d:' option; do
      case "$option" in
        f)
          FASTA+=("$OPTARG")
          ;;
        d)
          DIRECTORY="$OPTARG"
          ;;
        *)
          printf 'ERROR: Invalid argument\n' >&2
          exit 1
          ;;
      esac
    done
    shift $((OPTIND-1))
    
    if [[ -z ${FASTA[0]} ]]; then
      printf 'ERROR: FASTA1 missing\n' >&2
      exit 1
    fi
    
    if [[ -z $DIRECTORY ]]; then
      printf 'ERROR: DIRECTORY missing\n' >&2
      exit 1
    fi
    
    printf 'FASTA1 = %s\n' "${FASTA[0]}"
    printf 'FASTA2 = %s\n' "${FASTA[1]}"
    printf 'DIRECTORY = %s\n' "$DIRECTORY"
    

    用法:

    run -f file1.fasta -f file2.fasta -d /home/folder1/folder2
    

    getopt 与单字符和 GNU 样式长选项混合使用:

    #! /bin/bash
    
    FASTA=()
    DIRECTORY=
    
    options=$(getopt -o d: -l f1: -l f2: -- "$@") || {
      printf 'ERROR: Invalid argument\n' >&2
      exit 1
    }
    eval set -- "$options"
    
    while true; do
      case "$1" in
        --f1)
          FASTA[0]="$2"
          shift 2;;
        --f2)
          FASTA[1]="$2"
          shift 2;;
        -d)
          DIRECTORY="$2"
          shift 2;;
        --)
          shift
          break;;
        *)
          break;;
      esac
    done
    
    if [[ -z ${FASTA[0]} ]]; then
      printf 'ERROR: FASTA1 missing\n' >&2
      exit 1
    fi
    
    if [[ -z $DIRECTORY ]]; then
      printf 'ERROR: DIRECTORY missing\n' >&2
      exit 1
    fi
    
    printf 'FASTA1 = %s\n' "${FASTA[0]}"
    printf 'FASTA2 = %s\n' "${FASTA[1]}"
    printf 'DIRECTORY = %s\n' "$DIRECTORY"
    

    用法:

    run --f1 file1.fasta --f2 file2.fasta -d /home/folder1/folder2
    

    【讨论】:

    • getopt[s] 增加了不必要的复杂性,只是为了能够在单个参数中指定两个或多个短选项。 getopt[s] 也有局限性。它与长选项有关,也可能与可选参数有关。
    • @konsolebox 这既不复杂也没有必要。您的第二和第三句话只是表明没有阅读我的答案。
    • 我不知道去哪里找。你能谈谈你的主要观点吗?这个例子并没有使使用 getopt 或 getopts 实现选项解析的难度无效。有许多变体不能适用于它们。我希望我能回忆起这些限制。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-06
    • 2014-10-28
    • 2011-04-27
    • 1970-01-01
    • 1970-01-01
    • 2017-05-02
    相关资源
    最近更新 更多