重击和参数传递


8

我有以下简化的bash脚本

#!/bin/bash

files=("$@")

if [ "X$files" = "X" ]; then
  files=$HOME/print/*.pdf;
fi

for file in "${files[@]}"; do
  ls "$file";
done

如果我将参数(文件名)作为参数传递,则此脚本将打印正确的文件名。另一方面,如果我不传递参数,它将打印

/home/user/print/*.pdf: No such file or directory

为什么在这种情况下不能扩展文件名,我该如何解决?请注意,我使用files=("$@")and "${files[@]}"构造,因为我读到它比通常的“ files = $ *”更可取。


哪里是files=$*平常?那是完全错误的
斯特凡Chazelas

通常是相对的,对的。我的意思是不使用数组的方法。那你怎么办
highsciguy

Answers:


10

您将分配files标量变量而不是数组变量。

 files=$HOME/print/*.pdf

你分配一些字符串一样/home/highsciguy/print/*.pdf$files标量(又名字符串)变量。

采用:

files=(~/print/*.pdf)

要么

files=("$HOME"/print/*.pdf)

代替。外壳程序会将这种globing模式扩展为文件路径列表,并将每个路径分配给$files array的元素。

全局扩展在分配时完成。

您不必使用非标准的sh功能,可以通过编写系统功能来sh代替bash这里的系统:

#!/bin/sh -

[ "$#" -gt 0 ] || set -- ~/print/*.pdf

for file do
  ls -d -- "$file"
done

set是分配"$@"位置参数数组。

另一种方法可能是将通配模式存储在标量变量中:

files=$HOME/print/*.pdf

并在扩展$files 变量时让外壳扩展全局。

IFS= # disable word splitting
for file in $files; do ...

在这里,因为$files不加引号(通常不应该这样做),所以它的扩展受单词拆分(我们在这里已禁用)和全局/文件名生成的影响。

因此,*.pdf 将扩展为匹配文件列表。但是,如果$HOME包含通配符,它​​们也可以被扩展,这就是为什么仍推荐使用数组变量的原因。


4

您可能已经看到过类似的东西,files=$*并且files=~/print/*.pdf在没有数组的老式shell中使用了then ls $files

不在双引号内的变量替换将变量的值解释为以空格分隔的Shell通配符模式列表,如果有的话,将用匹配的文件名替换。例如,after之后files=~/print/*.pdfls $files将其扩展为ls带有参数/home/highsciguy/print/bar.pdf/home/highsciguy/print/foo.pdf等等的东西。在这种情况下files=$*,此分配将传递给脚本的参数连接起来,并在其之间ls $files留有空格,然后将它们分开。

如果您的文件名包含空格或通配符,则所有这些信息都会分解,这就是为什么您不应该这样处理的原因。请改用数组。

files=("$@")
if ((${#files[@]} == 0)); then
  files=("$HOME"/print/*.pdf)
fi

注意

  • 所有数组分配都需要在数组值两边加上括号:var=(…)
  • 要测试数组是否为空,请检查其长度。"$files"files为未设置索引0的元素的数组或空字符串时为空。这[ "X$foo" = "X" ]也是测试是否$foo为空的过时方法:所有现代shell都[ -n "$foo" ]正确实现。在bash中,您可以使用[[ -n $foo ]]

在不支持数组的shell中,实际上只有一个数组:shell或当前函数的位置参数。在这里,您实际上并不需要files数组,实际上使用位置参数会更容易。

#!/bin/sh
if [ "$#" -eq 0 ]; then
  set -- ~/print/*.pdf
fi
for file do 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.