星号/双引号无法执行

星号/双引号无法执行

抱歉,我是 shellscripting 新手。我有一个变量,其中包含与命令相关的选项find

TYPE=("-type f")
NAME=("-name \"*log*\"")

我尝试将那些包含选项的变量放入相关代码中。

  • 这有效:
    LST_FILE=$(find ${2} ${TYPE} -mmin +${HOUR_TO_MIN})
    
    并产生(样本输出):
    viv/clean/prototype/asdadsadsa.log
    viv/clean/prototype/logo
    viv/clean/prototype/sadsadasdas.log.gz
    viv/clean/prototype/prototype/log
    viv/clean/prototype/prototype/fewfasfs
    viv/clean/prototype/prototype/og
    
  • 但是当我尝试添加它${NAME}无法捕获关键字名称来搜索我想要的文件时,它没有结果。
    LST_FILE=$(find ${2} ${TYPE} ${NAME} -mmin +${HOUR_TO_MIN})
    
    (产生 0 个结果)

似乎问题与通配符、特殊字符或类似的东西有关。非常感谢您对相关案例的建议。

答案1

这里有两个问题。第一个问题是您将两个变量定义为只有一个元素的数组,然后在命令中将它们用作find简单的标量变量。当您执行此操作时,bash 会礼貌地返回每个数组的第一个元素。

第二个,正如 C.Aknesil 指出的,bash 在扩展变量后不会删除双引号。事实上,这实际上并不是一个问题,问题是你不知道它或者为什么 bash 会这样表现——“这是一个特性,而不是一个错误”。

试试这样:

TYPE=(-type f)
NAME=(-name '*log*')

'*log*'需要在这里加引号,以便 bash 不会扩展 glob 并将当前目录中的所有匹配文件添加到数组中。

然后,当您需要将它们与以下内容一起使用时find

find "$2" "${TYPE[@]}" -mmin "+$HOUR_TO_MIN"

或者

find "$2" "${TYPE[@]}" "${NAME[@]}" -mmin "+$HOUR_TO_MIN"

顺便说一句,请注意,这里$2和都$HOUR_TO_MIN应该用双引号引起来,但{}不需要大括号,因为不需要将它们与周围的字符消除歧义。例如$2不需要{},但${2}3需要它们,因为它会被解释为$23(和不是如“$2 后跟 3”)由没有它们的 shell 执行。

$HOUR_TO_MIN不引用可能是可以的(因为它可能是在脚本中定义的,并且可能没有像空格等问题字符),但引用它并没有什么坏处,并且“总是引用你的变量(除非你知道绝对确定您不想这样做,并且为什么)”是一个值得养成的好习惯。

$2然而,由用户作为脚本的参数提供,并且其中可以包含任何内容,并且应该始终被引用。


注意:这样做并不是一个好主意,LST_FILES=$(find ...)因为文件名中可能包含各种烦人的字符,从空格、制表符和换行符等空白到;&>和 等shell 元字符|。唯一的角色是不能文件名中是 NUL,这意味着 NUL 是唯一可靠的文件名分隔符任何文件名。

相反,如果您需要在变量中查找 find 的输出,请使用数组并告诉find使用 NUL 作为带有-print0.例如

mapfile -d '' LST_FILES < <(find "$2" "${TYPE[@]}" "${NAME[@]}" -mmin "+$HOUR_TO_MIN" -print0)

mapfile命令(也称为readarray)从 stdin 读取并使用它来填充数组。在本例中,我们指示使用 NUL 作为 的分隔符-d ''

尝试执行以下操作,看看它对您自己有何效果:

$ mkdir junk
$ cd junk
$ touch foo bar baz 'file with spaces' $'file\nwith\nLFs'
$ ls
bar  baz  file?with?LFs  file with spaces  foo

$ mapfile -d '' LST_FILES < <(find . -type f -print0)

$ declare -p LST_FILES
declare -a LST_FILES=([0]="./foo" [1]=$'./file\nwith\nLFs' [2]="./baz"
[3]="./file with spaces" [4]="./bar")

$ echo "${#LST_FILES[@]}"   # use ${#...} to count elements in an array
5

$ for f in "${LST_FILES[@]}"; do echo "$f" ; done
./foo
./file
with
LFs
./baz
./file with spaces
./bar

答案2

我的结论是 Bash 不会在分词后对单个单词执行引号删除。因此,您提供的模式"*log*"中包含双引号。换句话说,您正在搜索名称以双引号开头和结尾的文件。

来自bash手册:

展开的顺序是:大括号展开;波形符扩展、参数和变量扩展、算术扩展和命令替换(以从左到右的方式完成);分词;和文件名扩展。 [...] 执行这些扩展后,原始单词中存在的引号字符将被删除,除非它们本身已被引用(引号删除)。

您可以使用以下代码观察此行为:

$ NAME=("-name \"*log*\"")
$ echo $NAME
-name "*log*"

正如您所看到的,$NAME提供给 的echo方式与提供给 的方式相同findecho打印双引号的事实表明,在调用 之前,模式周围的引号不会被删除echo

作为修复,我建议使用find不带变量的模式。要实现类似的代码分解,您可以创建一个使用 的函数find,并在后面调用该函数:

function find_files {
  find ... <pattern>
}

LST_FILE=$(find_files)

我找不到可以将模式存储到变量中的解决方案。我希望上面的解决方案足够了。

相关内容