如何迭代POSIX shell中的双引号字符串?

时间:2014-09-24 04:20:02

标签: shell for-loop posix

我正在尝试在脚本继续其主要作业之前检查我的脚本所依赖的所有非POSIX命令是否存在。这将帮助我确保我的脚本稍后不会因缺少命令而生成错误。

我希望将所有此类非POSIX命令的列表保存在名为DEPS的变量中,以便随着脚本的发展并依赖于更多命令,我可以编辑此变量。

我希望脚本支持带有空格的命令,例如my program

这是我的剧本。

#!/bin/sh
DEPS='ssh scp "my program" sftp'

for i in $DEPS
do
    echo "Checking $i ..."
    if ! command -v "$i"
    then
        echo "Error: $i not found"
    else
        echo "Success: $i found"
    fi
    echo
done

但是,这不起作用,因为"my program"for循环迭代时分为两个单词:"myprogram",如您所见下面的输出。

# sh foo.sh
Checking ssh ...
/usr/bin/ssh
Success: ssh found

Checking scp ...
/usr/bin/scp
Success: scp found

Checking "my ...
Error: "my not found

Checking program" ...
Error: program" not found

Checking sftp ...
/usr/bin/sftp
Success: sftp found

我预期的输出是:

# sh foo.sh
Checking ssh ...
/usr/bin/ssh
Success: ssh found

Checking scp ...
/usr/bin/scp
Success: scp found

Checking my program ...
Error: my program not found

Checking sftp ...
/usr/bin/sftp
Success: sftp found

如何在保持脚本POSIX兼容的同时解决此问题?

3 个答案:

答案 0 :(得分:2)

我将重复我之前提出的问题的答案:使用带有here文档而不是for循环的while循环。您可以在字符串中嵌入换行符,如果这些命令名称可能包含空格,则只需在字符串中分隔命令名称即可。 (如果您的命令名称包含换行符,请强烈考虑重命名它们。)

为了获得最大的POSIX兼容性,请使用printf,因为echo的POSIX规范非常宽松,因为在定义标准之前各种shell中echo的实现方式不同。

deps="ssh
scp
my program
sftp
"
while read -r cmd; do
    printf "Checking $cmd ...\n"
    if ! command -v "$cmd"; then
        printf "Error: $i not found\n"
    else
        printf "Success: $cmd found\n"
    fi
    printf "\n"
done <<EOF
$deps
EOF

答案 1 :(得分:1)

这是因为参数扩展后的步骤是字符串拆分和glob-expansion - 语法级解析(例如处理引用)。要一直回到解析过程的开头,您需要使用eval


坦率地说,最好的方法是:

  1. 定位支持数组(ksh,bash,zsh等)的shell,而不是尝试支持POSIX
  2. 不要尝试从变量中检索值。
  3. ......正确的阵列支持在现代贝壳中无处不在;明确地编写正确的代码,特别是在处理不受信任的数据时,如果没有它就会更难。


    也就是说,您可以选择使用$@来存储您的内容,使用eval可以设置,尽管是危险的:

    deps='goodbye "cruel world"'
    eval "set -- $deps"
    for program; do
      echo "processing $program"
    done
    

    如果在函数内部执行此操作,则只会覆盖函数的参数列表,而不会修改全局列表。

    或者,eval "yourfunction $deps"将具有相同的效果,将函数中的参数列表设置为运行$deps内容的所有常规解析和扩展阶段的结果。

答案 2 :(得分:1)

由于脚本在您的控制中,您可以合理安全地使用eval,因此@Charles Duffy的答案是一个简单而好的解决方案。用它。 :)

另外,请考虑使用autoconf来生成通常的configure脚本,这样可以满足您的需求 - 例如检查命令等等......至少,检查一些配置脚本,了解如何解决常见问题...

如果你想玩自己的实现:

  • 将家属分为两组
    • core_deps - unix工具,脚本本身通常需要的功能,例如sedcat cp等。这些程序的名称中不包含空格,也不包含$ PATH。
    • runtime_deps - 程序,您的应用程序需要什么,但不适用于脚本本身。
  • 分两步进行检查(或更多,例如,如果您需要检查,例如图书馆)
  • 永远不要将for循环用于空格分隔元素,除非您将它们作为函数参数 - 因此您可以使用"$@"

由于起始脚本可能如下所示:

_check_core_deps() {
    for _cmd
    do
        _cpath=$(command -v "$_cmd")
        case "$_cpath" in
        /*) continue;;
        *) echo "Missing install dependency [$_cmd] - can't continue" ; exit 1 ;;
        esac
    done
    return 0
}

core_deps="grep sed hooloovoo cp"   #list of "core" commands - they doesn't contains spaces
_check_core_deps $core_deps || exit 1

以上内容会炸毁不存在的“hooloovoo”命令。 :)

现在您可以安全地继续,安装脚本所需的所有核心命令都可用。在下一步中,您可以检查其他奇怪的依赖项。

一些想法:

# function what returns your dependecies as lines from HEREDOC
# (e.g. could contain any character except "\n")
# you can decorate the dependecies with comments...
# because we have sed (checked in the 1st step, can use it)
# if want, you can add "fields" too, for some extended functinality with an specified delimiter
list_deps() {
    _sptab=$(printf " \t")  # the $' \t' is approved by POSIX for the next version only
    #the "sed" removes comments and empty lines
    #the UUOC (useless use of cat) is intentional here
    #for example if you want add "tr" before the "sed"
    #of course, you can remove it...
    cat - <<DEPS |sed "s/[$_sptab]*#.*//;/^[$_sptab]*$/d"
########## DEPENDECIES ############
#some comment
ssh
scp
sftp
        #comment
#bla bla
my program  #some comment
/Applications/Some Long And Spaced OSX Apllication.app
DEPS
########## END of DEPENDECIES #####
}

_check_deps() {
#in the "while" loop you can use IFS=: or such and adding anouter variable to read 
#for getting more fields for some extended functionality
list_deps | while read -r line
do
    #do any checks with the line
    #implement additional functionalities as functions
    #etc...
    #remember - your in an subshell here
    printf "command:%s\n" "$line"
done
} 

_check_deps

还有一件事:),(或两件事)

  • 如果您怀疑某些变量的内容,请不要使用echo。 POSIX未定义包含转义字符时的行为方式(例如echo "some\nwed")。使用:
printf '%s' "$variable"
  • 从不使用仅大写的变量,如“DEPS”...它们仅用于环境变量......