如何处理变量中的空格

Duy*_*Duy 6 shell find shell-script recursive command-substitution

我正在编写一些脚本:

for x in `find ./ -name *.pdf`
do
  echo pathname $x
done
Run Code Online (Sandbox Code Playgroud)

我的文件名是Test1 ( Volume II), Test2 ( Volume II). 我得到了回报

pathname Test1
pathname (
pathname Volume
pathname II
…
Run Code Online (Sandbox Code Playgroud)

我如何让它保持作为一个变量?

Sté*_*las 9

正如本网站多次所说,在 Bourne/POSIX shell 中不加引号的变量扩展(如 in $var)或命令替换(如 in`cmd`$(cmd))(或算术扩展(如 in $((11 * 11)))在大多数 shell 中)是 split+glob 运算符。

根据特殊变量的当前值(默认情况下包含 SPC、TAB 和 NL 字符(以及 中的 NUL ))以及由此产生的每个单词,对内容$var或输出cmd(不带尾随换行符)进行拆分拆分受文件名生成的影响,也称为globbing$IFSzsh

例如,如果find输出./foo bar.pdf\n./*foo*\tbar.pdf\n\t表示 TAB 和\nNL),默认值为$IFS,则命令替换将扩展为./foo bar.pdf\n./*foo*\tbar.pdf(删除尾随换行符),然后拆分为./foobar.pdf , ./*foo*, 和foo.pdf./*foo*通配符模式将扩展为许多参数,因为当前目录中存在名称包含foo.

如果只想拆分换行符,则需要设置 $IFS为仅换行符:

IFS='
'
Run Code Online (Sandbox Code Playgroud)

如果您不想扩展通配符模式,则需要使用以下命令禁用它

set -f
Run Code Online (Sandbox Code Playgroud)

但是请注意,换行符与文件名中的任何字符一样有效,因此更一般地说,find -print输出不能可靠地进行后处理。

输出如下:

./a.pdf
./b.pdf
Run Code Online (Sandbox Code Playgroud)

要么表示当前目录中的a.pdfb.pdf文件,要么表示目录中调用b.pdfa.pdf\n.文件。

一些find实现,如 GNU find(它的起源)有一个-print0谓词来输出文件名,后跟 NUL 字符而不是 NL 字符。使用标准find,您可以使用-exec printf '%s\0' {} +相同的结果。NUL 是唯一不能出现在文件名中的字符。

但是,zsh它是唯一可以在其变量(如$IFS字符)中存储 NUL 字符的 shell ,因此:

IFS=$'\0'
for i in `find ... -print0`; do
  ...
done
Run Code Online (Sandbox Code Playgroud)

(不需要set -finzsh因为zsh在命令替换时不进行 globbing)将zsh在其他 shell 中工作,但不能在其他 shell 中工作。

最好且可移植的是find调用您要在这些文件上运行的命令。正如@Gnouc 所建议的:

find . -name '*.pdf' -exec the command {} \;
Run Code Online (Sandbox Code Playgroud)

如果您需要更复杂的 shell 语句,您仍然可以执行以下操作:

find . -name '*.pdf' -exec sh -c '
  for i do
    something complex with "$i"
  done' sh {} +
Run Code Online (Sandbox Code Playgroud)

使用zshbash,您还可以执行以下操作:

find . -name '*.pdf' -print0 |
  while IFS= read -r -d '' file; do
    whatever with "$file"
  done
Run Code Online (Sandbox Code Playgroud)

但是请注意,循环中的 stdin 会受到影响。

zsh(自 1990 年以来)find通过一种语法将大部分功能包含在其通配功能中,您可以在其中指定任何级别的子目录((*/)#语法或其更简单的形式**/)和通配限定符(它们是-type f, -mtime, -perm... infind) .

其中的**/一部分ksh93在 2003 年、fish2005 年、bash2009 年和tcsh2010 年tcsh被复制(尽管也复制了该***/部分)。并且所有这些默认情况下都不启用它。不幸的是,请注意,bashfish **都遵循指向目录的符号链接(例如-L/ -followin find,或***inzshtcsh)。

在这些 shell 中,您可以pdf在任何级别的子目录中查找文件而不必依赖find,但请注意上面关于fish和的警告bash,并且仅zsh通配符限定符。

因此,例如,zsh相当于:

find . -name '*.pdf' -type f -exec ls -ld {} +
Run Code Online (Sandbox Code Playgroud)

将是:

ls -ld ./**/*.pdf(D.)
Run Code Online (Sandbox Code Playgroud)

使用 时bash,您必须执行以下操作:

shopt -s failglob
shopt -s globstar
files=(./**/*.pdf) &&
  for i do
    [ -f "$i" ] && ! [ -L "$i" ] && set -- "$i" "$@"
    shift
  done && ls -ld "$@"
Run Code Online (Sandbox Code Playgroud)


ter*_*don 4

最安全的方法是使用 globbing:

for file in *pdf; do echo pathname "$file"; done
Run Code Online (Sandbox Code Playgroud)

如果您需要递归查找所有 pdf,请执行以下操作:

shopt -s globstar
for file in **/*pdf; do echo pathname "$file"; done
Run Code Online (Sandbox Code Playgroud)