Answers:
bash中一种可靠的方法是扩展为数组,并仅输出第一个元素:
pattern="*.txt"
files=( $pattern )
echo "${files[0]}" # printf is safer!
(您甚至可以echo $files
将丢失的索引视为[0]。)
扩展文件名时,这可以安全地处理空格/制表符/换行符和其他元字符。请注意,有效的语言环境设置可以更改“ first”。
您还可以使用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
这会将_echo
函数绑定到echo
命令的完成参数(覆盖正常完成)。上面的代码中附加了一个额外的“ *”,您可以单击部分文件名上的tab键,希望会发生正确的事情。
代码有些复杂,而不是设置或假设nullglob
(shopt -s nullglob
),我们检查compgen -G
可以将glob扩展到某些匹配项,然后安全地扩展到数组中,最后设置COMPREPLY以使引用更可靠。
您可以使用bash的一部分执行此操作(以编程方式扩展glob)compgen -G
,但是它不健壮,因为它会将未引用的内容输出到stdout。
像往常一样,完成工作充满烦恼,这破坏了其他事情的完成,包括环境变量(有关仿真默认行为的详细信息,请参见此处的_bash_def_completion()
函数)。
您也可以在compgen
完成功能之外使用:
files=( $(compgen -W "$pattern") )
需要注意的一点是“〜”不是一个全局变量,它是由bash在扩展的单独阶段处理的,$变量和其他扩展也是如此。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]}"
}
然后:
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 )
此选项使您可以更好地控制排序顺序(扩展全局范围时的顺序将取决于您的语言环境/,LC_COLLATE
并且可能会(也可能不会)折叠大小写),但对于这么小的问题,它还是一个很大的锤子;-)
在zsh中,使用[1]
glob限定符。请注意,即使这种特殊情况最多返回一个匹配项,它仍然是一个列表,并且在期望使用单个单词(例如赋值(不包括数组赋值))的上下文中,不会扩展glob。
echo *.txt([1])
在ksh或bash中,您可以将整个匹配项列表填充到数组中并使用第一个元素。
tmp=(*.txt)
echo "${tmp[0]}"
在任何外壳程序中,您都可以设置位置参数并使用第一个。
set -- *.txt
echo "$1"
这使位置参数变得混乱。如果您不想这样做,则可以使用子外壳。
echo "$(set -- *.txt; echo "$1")"
您还可以使用一个函数,该函数具有自己的一组位置参数。
set_to_first () {
eval "$1=\"\$2\""
}
set_to_first f *.txt
echo "$f"
*.txt([1,n])
一个简单的解决方案:
sh -c 'echo "$1"' sh *.txt
或根据printf
需要使用。
我在想知道同一件事的同时,偶然发现了这个老问题。我结束了这个:
echo $(ls *.txt | head -n1)
当然,您可以head
用tail
和替换-n1
为任何其他数字。
如果您正在使用名称中包含换行符的文件,则上述方法将无效。要使用换行符,可以使用以下任意一种:
ls -b *.txt | head -n1 | sed -E 's/\\n/\n/g'
(不适用于BSD)ls -b *.txt | head -n1 | sed -e 's/\\n/\'$'\n/g'
ls -b *.txt | head -n1 | perl -pe 's/\\n/\n/g'
echo -e "$(ls -b *.txt | head -n1)"
(可以使用任何特殊字符)我经常遇到的用例是在glob扩展后定义top / bottom目录(例如,充满版本SDK或构建工具的目录)。在这种情况下,我通常要将该目录名保存到一个变量中,以便在Shell脚本中的几个地方使用。
这个命令通常为我做:
export SDK_DIR=$(dirname /path/to/versioned/sdks/*/. | tail -n1)
免责声明:Glob扩展不会按字母顺序对文件夹进行排序;您已被警告。如果您Dockerfile
只有一个版本的目录,但是该目录的版本可能因映像而异,那么这很好。
mkdir "$(echo one; echo two)"
,看看我的意思。
tail
?
dirname
仅采用一个路径名,因此除非您知道特定的实现支持它,否则您不能依靠它在多个路径名上工作。