Bash 从多个变量中获取变量的快捷方式

Bash shortcut for take the variable that exists from many

这个问题我已经有一段时间了。在我使用 bash 脚本的工作中,有很多情况我必须将文件路径保存到变量以供以后的命令使用。我们使用后缀为 T1w、T2w 和掩码的文件,在许多情况下我们有一个优先文件(例如 T1ws),但如果没有,我们将使用另一个文件(例如 T2w)。我通常使用以下 bash 语法来搜索适当的文件并将其保存到变量中(假设我在当前目录中搜索):

if [ -f xxx_T1w ]; then
  var1=xxx_T1w
elif [ -f xxx_T2w ]; then
  var1=xxx_T2w
fi

很好,它可以工作,但我一直很好奇 bash 中是否有一种方法可以编写如下代码: 如果两个给定文件之一存在,则取一个存在的文件, 如果两者都存在,取第一个文件。 我对伪代码很在行,但可能是这样的:

if [ -f file_T1w ] || [ -f file_T2w ]; then
  var1=existing file
fi

我经常重复使用此代码,有时 elif 语句会变得很长,如果有更长的优先文件列表我们可以遍历,我一直想知道是否有更多紧凑的方式来做到这一点。

使用这个 Perl one-liner(它使用 1 个或多个文件作为参数):

var1=$( perl -MList::Util='first' -le 'print first { -f $_ } @ARGV;' xxx_T1w xxx_T2w )

示例(请注意,如果列出的文件中有 none 个存在,则 $var1 将为空):

$ rm xxx_T1w
$ touch xxx_T2w
$ var1=$( perl -MList::Util='first' -le 'print first { -f $_ } @ARGV;' xxx_T1w xxx_T2w )
$ echo ${var1}
xxx_T2w
$ var1=$( perl -MList::Util='first' -le 'print first { -f $_ } @ARGV;' xxx_T2w xxx_T1w )
$ echo ${var1}                                                                          
xxx_T2w
$ var1=$( perl -MList::Util='first' -le 'print first { -f $_ } @ARGV;' xxx_T1w )        
$ echo ${var1}                                                                  

$

Perl one-liner 使用这些命令行标志:
-e :告诉 Perl 查找代码 in-line,而不是在文件中。
-l : 在执行代码 in-line 之前去除输入行分隔符(默认情况下在 *NIX 上为 "\n"),并在打印时附加它。
-MList::Util='first' : 使用模块 List::Util,并从中导入子程序 first。这个标准库模块包含在您的 Perl 发行版中(无需额外安装)。

@ARGV :命令行参数数组(此处为文件)。

print first { -f $_ } @ARGV; :打印在命令行上作为参数列出的第一个文件,该文件存在并且是文件(不是目录)。

另见:
perldoc perlrun: how to execute the Perl interpreter: command line switches
perldoc perlvar: Perl predefined variables
List::Util::first

我能想到的最有希望的是循环:

for f in file1 file2; do
    if [ -f "$f" ]; then
        var="$f"
        break
    fi
done

您可以根据需要使用 && 来缩短代码,但要以支持 errexit shell 选项为代价。对于只有两个文件,这种方法的好处是值得怀疑的,因为它与您已经编写的 if/elif 语句的代码量大致相同,而且它试图做什么可能不太清楚.但对于许多文件来说,这将更加简洁。

正如其他人所建议的那样,我认为将其封装在 shell 函数中绝对是一件好事。请记住,变量赋值不是(必须)作用于 bash 中的 shell 函数,因此您可以毫无问题地从函数内部设置变量并在其他地方使用它;但是,如果对您更有用,您也可以考虑让函数将找到的文件的名称打印到标准输出。

如果你把它放在一个函数中,你实际上可以使用 shell 的 built-in 循环位置参数的能力,就像这样:

find_file() {
    for f; do
        if [ -f "$f" ]; then
            var="$f"
            return
        fi
    done
}

Bash 函数 return 仅状态代码,因此没有 return 值可分配给变量,就像您在使用标准编程语言时所做的那样,例如 var = get_file(file1 file2).

但是你可以定义一个函数,有这个方便的方法

get_file var file1 file2 file3

这是您可以在脚本中使用的简短可重用代码。


修改@David Z的,你的函数可以参考 第一个参数的名称,在 shift 之后,迭代其余参数以测试它们。这里有一个陷阱,参见 this,你必须避免循环名称引用,我会选择一个难以在你的其余脚本中存在的变量名。

function get_file() {
    declare -n get_file_var=
    shift
    for f; do
        if [ -f "$f" ]; then
            get_file_var="$f"
            return 0
        fi
    done
    return 1
}

return 状态可用于确定是否发生任何分配。

get_file var file1 file2 file3
echo $?
echo $var

或者这个:

if get_file var file1 file2; then 
    echo var was assigned $var
else
    echo no file assigned # var can be empty string or keep its previous value
fi

你也可以用数组来调用它:

arr=("missing filename" file1 file2)
get_file x "${arr[@]}"