我需要能够列出变量中指定的文件,如下例所示:
X="myfile.txt"
$ ls ${X}
遗憾的是,有些人在文件名中使用空格,因此可能是这种情况:
X="my file.txt"
可以用以下方法解决ls "${X}"
然而,这也可能发生:
X="my file.*"
这会破坏,因为通配符不会在双引号内扩展。
有什么东西可以适用于所有情况吗?
编辑
声明X
为数组,如
X=("my file."*)
ls "$X[@]"
是不够的,因为它在声明时扩展了通配符。我需要它在被使用时扩展ls
。
答案1
巴什
在 中bash
,将变量不加引号有时称为 split+glob 运算符。
分割部分可以使用$IFS
特殊参数进行调整,全局部分可以使用以下noglob
选项进行调整:
仅对于通配符,设置$IFS
为空字符串:
file_pattern='foo bar *.txt'
IFS= # disable splitting
set +o noglob # make sure glob is enabled
ls -ld -- $file_pattern # split+glog with split disabled.
请注意,如果模式与任何文件都不匹配,ls
将收到一个文字foo bar *.txt
参数并抱怨该文件不存在。
仅用于拆分(此处以拆分冒号分隔的$PATH
变量为例),设置noglob
选项:
IFS=: # split on :
set -o noglob
ls -ld -- $PATH # split+glob with glob disabled
当$var
为空(或未设置)时,请注意,即使为空,其展开也不产生任何参数,而是一个空参数$IFS
。
这里可以接受,因为$PATH
空$PATH
(但不是 unset $PATH
)意味着在当前工作目录中搜索命令,并且ls
在未传递任何参数时也恰好列出当前工作目录。
$PATH
如果(如)中有空元素/bin::/usr/bin
,这也意味着当前工作目录,则会传递一个相应的空参数,ls
该参数将对此进行抱怨。一个例外是,如果该空元素是最后一个(如 中/bin:/usr/bin:
),则最后一个元素将被丢弃。
分割类似变量的更正确方法$PATH
是使用:
IFS=:
set -o noglob
printf '<%s>\n' $PATH''
这一额外的部分''
可以防止删除尾随的空元素,并确保将空元素$PATH
拆分为一个空元素,而不是根本没有元素。
桀骜
在 zsh 中,分割和通配符并不是在参数扩展时隐式完成的,您必须使用$=var
for $IFS
-splitting、${(s[separator])var}
基于任意分隔符的分割、$~var
通配符(或$=~var
组合$IFS
-splitting 和通配符)显式请求它们。
所以:
file_pattern='foo bar *.txt'
ls -ld -- $~file_pattern
(如果模式与任何文件都不匹配,则命令ls
将中止²)。
IFS=:
ls -ld -- $=PATH # preserves all empty elements even trailing ones
ls -ld -- ${(s[:])PATH} # split, discarding empty elements
ls -ld -- "${(@s[:])PATH}" # split, preserving empty elements
¹ 除非启用该选项(来自由而不是failglob
管理的另一组选项),在这种情况下 bash 将中止(退出子 shell 或如果不在子 shell 中运行则跳过它已读取的所有代码),或者启用该选项在这种情况下,它将扩展为空,导致列出当前工作目录。shopt
set
nullglob
shopt
ls
² 在 zsh 中,失败的 glob 的处理方式类似于语法错误,相应的 shell 进程会以错误和失败退出状态退出(尽管在交互式 shell 中,它将返回到提示符而不是退出主 shell)。在这里,与ls
外部命令一样,只有为执行它而生成的子进程才会被中止,因为这就是全局扩展发生的地方。ls
例如,如果您重新定义为 shell 函数,情况会有所不同。
答案2
如果您想使用通配符,那么使用变量并不是一个好主意,因为它可能会匹配多个文件。
解决办法:使用数组!
X=("my file."*)
ls "${X[@]}"
# or
for f in "${X[@]}"; do
some_command "$f"
done
请注意,在赋值时,*
必须位于引号之外。
查看这里用于仅在变量扩展时扩展全局的解决方案。