如何可移植地解析脚本命令行参数?

问题描述 投票:0回答:1

我有一个用于截屏的脚本。它在后台运行

maim
并提供比仅运行命令本身更方便的界面(更少输入,更简单,助记选项,将屏幕截图保存到具有唯一名称的目录)。该脚本的基本用法(假设它位于 $PATH 中)是:
screenshot <opts> <screenshot_type>

#!/bin/dash

# Set the right default permissions for files
umask 077

# Directory where screenshots are saved
screenshots_directory="$HOME"/pictures/screenshots

# Notifications (default none)
notifications=0

# Error messages
directory_error_message="$screenshots_directory is not properly configured. Halting."
flag_error_message="contains an invalid flag. Halting."
type_error_message="is an invalid screenshot type. Halting."
screenshot_error_message="Failed to take the screenshot."

# Exit handler (exit status, stdout, stderr, notifications)
exit_handler () {
    exit_code=$1
    exit_notification_title="$2"
    message="$3"
    notify_opts=' '
    if [ "${exit_code}" -gt 0 ]; then
        notify_opts='--urgency=critical --expire-time=7000'
        echo "${message}" 1>&2
    else
        echo "${message}"
    fi
    shift 3
    [ "$notifications" -eq 1 ] && notify-send $notify_opts "${exit_notification_title}" "${*:-$message}"
    exit "$exit_code"
}

# Parse optional flags, save them and remove them from the positional parameters
opts=''
curropt=''
optgroup=''
virtoptgroup=''
while getopts "bcdn" curropt; do
    paramgroup="$1"
    case "$curropt" in
        b) flag="--capturebackground" ;;
        c) flag="--hidecursor" ;;
        d) flag="--delay=0.2 --quiet" ;;
        n) notifications=1 ;;
        *) exit_handler 1 "Error" "'${1}' $flag_error_message" ;;
    esac
    virtoptgroup="${virtoptgroup}${curropt}"
    optgroup="${optgroup} ${flag}"
    if [ "-${virtoptgroup}" = "$paramgroup" ]; then
        opts="${opts}${optgroup}"
        shift 1
        virtoptgroup=''
        optgroup=''
    fi
done

echo "$#"

脚本的这一部分应该解析参数并将它们从位置参数中删除,或者如果提供了无效参数,则退出脚本并显示错误。为了说明目的,添加了打印位置参数数量的 echo 语句。为了重现该问题,请将其复制到脚本中并按如下方式运行:

your_script_name -b -c full
。它使用 dash 正确运行,并输出 1。将 shebang 更改为
#!/bin/bash
#!/bin/zsh
#!/bin/ksh
并运行相同的命令。输出将大于 1,这意味着参数没有被正确解析;这表明脚本的后半部分将发生错误。

此行为已在 Arch Linux 上使用以下版本的 shell 进行了测试: 破折号 0.5.12-1 bash 5.1.016-4 zsh 5.9-4 ksh 202.0.0-3

简而言之,脚本的工作方式是,它获取通过 getopts 给出的命令行参数,根据这些参数为

maim
创建一组标志,并使用
shift
从位置参数中删除它们,所以完成选项后,
$1
是它应该采用的屏幕截图类型。在我编写脚本时,这对我来说似乎是一个很好的解决方案。我不希望它成为一个拼凑在一起的黑客,而是一个我可以在未来学习并扩展/重用其中部分的脚本。我的实现的一个好处是选项不一定需要用空格分隔(因此类似
screenshot -cn full
的效果就像
screenshot -c -n full
一样)。

我的目标是在我的脚本中实现可移植性和 POSIX 兼容性,因此我只使用 POSIX 命令调用,我使用 shellcheck 检查我的脚本并将

/bin/sh
设置为
dash
。最初,我以为我已经实现了这一点,但是,在使用
bash
zsh
ksh
测试脚本后,我发现解析命令行参数的方式仅适用于
dash
(所以如果 shebang是
#!/bin/sh
并且
/bin/sh
dash
或者如果 shebang 是
#!/bin/dash
)。

考虑到上述所有情况,我应该如何解决问题并使我的脚本可移植?我对论点的解析是否略有错误,或者我应该以完全不同的方式处理问题?

bash shell posix zsh getopts
1个回答
0
投票

如果没有更多信息,很难准确地确定出了什么问题 - 你说它只在特定的 shell 中工作,但没有说在其他 shell 中发生了什么,并且很难从代码中确定可能发生的情况。

不过,有一些事情可以说:

  • 每个 shell 都有某种描述的“POSIX 模式”,这通常不是默认模式,但在作为
    sh
    调用时使用。不幸的是,某些 shell 中的默认模式不符合 POSIX 标准,并且与标准的不同之处难以预测,而且每个 shell 的默认模式都不同。如果不使用 shell 的 POSIX 模式,某些命令可以执行非常不同的操作。在某些情况下,只要将语言环境设置为 C
    POSIX
    ,那么一切都会按预期工作,但是,GNU 实用程序还需要设置
    POSIXLY_CORRECT
    ,而在某些情况下可能需要更改 shell 特定选项。
    正如其他人所指出的,您以不符合预期的方式使用 
  • getopts
  • ;您不应该在调用
    shift
    的循环中使用
    getopts
    您正在做的参数处理似乎比您想做的事情要复杂得多。
  • [ "-${virtoptgroup}" = "$paramgroup" ]
  • 可能会导致问题 - 如果
    ${virtoptgroup}
    包含的值恰好与
    test
    的选项匹配,那么它可能会按原样使用,并且可能不会执行您期望的操作(取决于版本的合理/标准遵从程度)
    test
    是)。在两侧使用已知前缀会更安全,例如:
    [ "X-${virtoptgroup}" = "X$paramgroup" ]
    - 这可确保它始终按预期工作,因为
    test
    没有以
    X
    开头的选项。或者,您可以使用
    case
    进行此类匹配 - 它更快且不易出错。
    
    
  • 最后,一个无耻的自我插件 - 您可能会对

BetterScripts POSIX Suite

 中的 
getarg
/libgetargs.sh 感兴趣,它类似于 getopts,但功能更强大,但仍然完全符合 POSIX 标准(并且经测试可在您提到的所有 shell 中工作)。即使您不使用它,您也可能会发现该代码有助于理解如何处理参数。
    

© www.soinside.com 2019 - 2024. All rights reserved.