如何处理变量中的空格

如何处理变量中的空格

我正在编写一些脚本:

for x in `find ./ -name *.pdf`
do
  echo pathname $x
done

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

pathname Test1
pathname (
pathname Volume
pathname II

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

答案1

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

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

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

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

IFS='
'

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

set -f

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

输出如下:

./a.pdf
./b.pdf

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

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

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

IFS=$'\0'
for i in `find ... -print0`; do
  ...
done

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

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

find . -name '*.pdf' -exec the command {} \;

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

find . -name '*.pdf' -exec sh -c '
  for i do
    something complex with "$i"
  done' sh {} +

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

find . -name '*.pdf' -print0 |
  while IFS= read -r -d '' file; do
    whatever with "$file"
  done

但请注意,循环内的标准输入会受到影响。

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

其中的部分在2003年、2005年、2009年和2010年**/被复制(尽管也复制了该部分)。并且它们都默认不启用它。不幸的是,请注意,和都遵循目录的符号链接(例如/ in 、或in or )。ksh93fishbashtcshtcsh***/bashfish **-L-followfind***zshtcsh

在这些 shell 中,您可以pdf在任何级别的子目录中查找文件,而不必依赖,但请注意上面的有关 about和find的警告,并且只有globbing 限定符。fishbashzsh

因此,例如,zsh相当于:

find . -name '*.pdf' -type f -exec ls -ld {} +

将会:

ls -ld ./**/*.pdf(D.)

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

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

答案2

最安全的方法是使用 globbing:

for file in *pdf; do echo pathname "$file"; done

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

shopt -s globstar
for file in **/*pdf; do echo pathname "$file"; done

答案3

尝试一下这一行:

find ./ -name "*.pdf" -exec echo pathname {} \;

答案4

在花了几个小时寻找默认 FreeBSD shell (/bin/sh) 的解决方案后,我终于想出了自己的解决方案。它涉及使用 sed 销毁/创建空格。这还允许您更改循环内的变量并在外部使用它们,这与使用 while 循环不同(while 循环在 /bin/sh 中生成子 shell)。

REMOTE_FOLDER=/media/images
FILE_LIST=$(find $REMOTE_FOLDER | sed 's/ /\/\/\/\//g')
for FILE in $FILE_LIST; do
    FILE=$(echo $FILE | sed 's/\/\/\/\// /g')
    # do whatever you need to do with $FILE
done

相关内容