像 Bash 和 Zsh 这样的 Shell 将通配符扩展为参数,与模式匹配的参数数量:
$ echo *.txt
1.txt 2.txt 3.txt
Run Code Online (Sandbox Code Playgroud)
但是如果我只想返回第一场比赛,而不是所有比赛呢?
$ echo *.txt
1.txt
Run Code Online (Sandbox Code Playgroud)
我不介意特定于 shell 的解决方案,但我想要一个适用于文件名中的空格的解决方案。
mr.*_*tic 38
在 bash 中,一个强大的方法是扩展为一个数组,并只输出第一个元素:
pattern="*.txt"
files=( $pattern )
echo "${files[0]}" # printf is safer!
Run Code Online (Sandbox Code Playgroud)
(您甚至可以echo $files将缺失的索引视为 [0]。)
这在扩展文件名时可以安全地处理空格/制表符/换行符和其他元字符。请注意,有效的语言环境设置可以改变“第一”是什么。
您还可以使用 bash完成功能以交互方式执行此操作:
_echo() {
local cur=${COMP_WORDS[COMP_CWORD]} # string to expand
if compgen -G "$cur*" > /dev/null; then
local files=( ${cur:+$cur*} ) # don't expand empty input as *
[ ${#files} -ge 1 ] && COMPREPLY=( "${files[0]}" )
fi
}
complete -o bashdefault -F _echo echo
Run Code Online (Sandbox Code Playgroud)
这将_echo函数绑定到echo命令的完整参数(覆盖正常完成)。上面的代码中附加了一个额外的“*”,您只需点击部分文件名上的选项卡,希望会发生正确的事情。
代码有点复杂,而不是设置或假设nullglob( shopt -s nullglob) 我们检查compgen -G可以将 glob 扩展为一些匹配项,然后我们安全地扩展为一个数组,最后设置 COMPREPLY 以便引用是健壮的。
您可以使用 bash 的部分执行此操作(以编程方式扩展 glob)compgen -G,但它并不健壮,因为它不带引号地输出到 stdout。
像往常一样,完成相当令人担忧,这会破坏其他事情的完成,包括环境变量(有关模拟默认行为的详细信息,请参阅此处的_bash_def_completion()函数)。
您也可以在compgen完成功能之外使用:
files=( $(compgen -W "$pattern") )
Run Code Online (Sandbox Code Playgroud)
需要注意的一点是,"~" 不是 glob,它由 bash 在单独的扩展阶段处理,$variables 和其他扩展也是如此。compgen -G只是进行文件名通配,但compgen -W会为您提供 bash 的所有默认扩展,尽管扩展可能太多(包括``和 $())。与 不同-G,-W 被安全引用(我无法解释差异)。由于 的目的-W是扩展令牌,这意味着即使不存在此类文件,它也会将“a”扩展为“a”,因此它可能并不理想。
这更容易理解,但可能会产生不必要的副作用:
_echo() {
local cur=${COMP_WORDS[COMP_CWORD]}
local files=( $(compgen -W "$cur") )
printf -v COMPREPLY %q "${files[0]}"
}
Run Code Online (Sandbox Code Playgroud)
然后:
touch $'curious \n filename'
echo curious*tab
请注意使用printf %q来安全地引用值。
最后一种选择是在 GNU 实用程序中使用 0 分隔的输出(请参阅bash FAQ):
pattern="*.txt"
while IFS= read -r -d $'\0' filename; do
printf '%q' "$filename";
break;
done < <(find . -maxdepth 1 -name "$pattern" -printf "%f\0" | sort -z )
Run Code Online (Sandbox Code Playgroud)
此选项可让您更好地控制排序顺序(扩展 glob 时的顺序将取决于您的语言环境/LC_COLLATE并且可能会或可能不会折叠大小写),但否则对于这样一个小问题来说是一个相当大的锤子 ;-)
Gil*_*il' 27
在 zsh 中,使用[1] glob 限定符。请注意,即使这种特殊情况最多返回一个匹配项,它仍然是一个列表,并且在期望单个单词的上下文中不会扩展 globs,例如赋值(数组赋值除外)。
echo *.txt([1])
Run Code Online (Sandbox Code Playgroud)
在 ksh 或 bash 中,您可以将整个匹配列表填充到一个数组中并使用第一个元素。
tmp=(*.txt)
echo "${tmp[0]}"
Run Code Online (Sandbox Code Playgroud)
在任何 shell 中,您都可以设置位置参数并使用第一个。
set -- *.txt
echo "$1"
Run Code Online (Sandbox Code Playgroud)
这破坏了位置参数。如果你不想那样,你可以使用子shell。
echo "$(set -- *.txt; echo "$1")"
Run Code Online (Sandbox Code Playgroud)
您还可以使用一个函数,它有自己的一组位置参数。
set_to_first () {
eval "$1=\"\$2\""
}
set_to_first f *.txt
echo "$f"
Run Code Online (Sandbox Code Playgroud)
尝试:
for i in *.txt; do printf '%s\n' "$i"; break; done
1.txt
Run Code Online (Sandbox Code Playgroud)
请注意,文件名扩展是根据当前语言环境中有效的整理顺序进行排序的。