当前位置:  开发笔记 > 编程语言 > 正文

如何在bash循环列表中转义空格?

如何解决《如何在bash循环列表中转义空格?》经验,为你挑选了4个好方法。

我有一个bash shell脚本循环遍历某个目录的所有子目录(但不是文件).问题是某些目录名称包含空格.

以下是我的测试目录的内容:

$ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt

以及遍历目录的代码:

for f in `find test/* -type d`; do
  echo $f
done

这是输出:

test/Baltimore
test/Cherry
Hill
test/Edison 
test/New
York
City
test/Philadelphia

Cherry Hill和纽约市被视为2或3个单独的条目.

我尝试引用文件名,如下所示:

for f in `find test/* -type d | sed -e 's/^/\"/' | sed -e 's/$/\"/'`; do
  echo $f
done

但无济于事.

必须有一个简单的方法来做到这一点.


以下答案很棒.但为了使这更复杂 - 我并不总是想使用我的测试目录中列出的目录.有时我想将目录名称作为命令行参数传递.

我接受了查尔斯关于设置IFS的建议,并提出了以下建议:

dirlist="${@}"
(
  [[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'\n'
  for d in $dirlist; do
    echo $d
  done
)

除非命令行参数中有空格(即使引用了这些参数),这也可以正常工作.例如,像这样调用脚本:test.sh "Cherry Hill" "New York City"产生以下输出:

Cherry
Hill
New
York
City

Charles Duff.. 103

首先,不要这样做.最好的方法是find -exec正确使用:

# this is safe
find test -type d -exec echo '{}' +

另一种安全方法是使用NUL终止列表,但这需要您的查找支持-print0:

# this is safe
while IFS= read -r -d '' n; do
  printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d -print0)

您还可以从find填充数组,并稍后传递该数组:

# this is safe
declare -a myarray
while IFS= read -r -d '' n; do
  myarray+=( "$n" )
done < <(find test -mindepth 1 -type d -print0)
printf '%q\n' "${myarray[@]}" # printf is an example; use it however you want

如果您的查找不支持-print0,那么您的结果就是不安全的 - 如果文件中存在包含其名称中的换行符(以下是合法的),则以下内容将无法按预期运行:

# this is unsafe
while IFS= read -r n; do
  printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d)

如果不打算使用上述方法之一,第三种方法(在时间和内存使用方面效率较低,因为它在进行分词之前读取子进程的整个输出)是使用一个IFS变量不包含空格字符.关闭通配符(set -f),以防止含水珠字符,例如字符串[],*?从被扩展:

# this is unsafe (but less unsafe than it would be without the following precautions)
(
 IFS=$'\n' # split only on newlines
 set -f    # disable globbing
 for n in $(find test -mindepth 1 -type d); do
   printf '%q\n' "$n"
 done
)

最后,对于命令行参数的情况,如果你的shell支持它们,你应该使用数组(即它是ksh,bash或zsh):

# this is safe
for d in "$@"; do
  printf '%s\n' "$d"
done

将保持分离.请注意,引用(以及使用$@而不是$*)很重要.数组也可以用其他方式填充,例如glob表达式:

# this is safe
entries=( test/* )
for d in "${entries[@]}"; do
  printf '%s\n' "$d"
done

我以前从未见过$'\n'语法.这是如何运作的?(我原以为IFS ='\n'或IFS ="\n"都可以使用,但两者都没有.) (2认同)


Johannes Sch.. 26

find . -type d | while read file; do echo $file; done

但是,如果文件名包含换行符,则不起作用.当你真正想要在变量中使用目录名时,以上是我所知道的唯一解决方案.如果您只想执行某些命令,请使用xargs.

find . -type d -print0 | xargs -0 echo 'The directory is: '

@Charles:对于大量文件,xargs效率更高:它只产生一个进程.-exec选项为每个文件分配一个新进程,速度可能慢一个数量级. (4认同)

亚当,不,'+'会聚合尽可能多的文件名,然后执行.但它没有像并行运行这样的整洁功能:) (2认同)

请注意,如果您想对文件名执行某些操作,则必须引用它们.例如:`找到.-type d | 同时读取文件; 做ls"$ file"; done` (2认同)


cbliard.. 22

这是一个处理文件名中的制表符和/或空格的简单解决方案.如果你必须处理文件名中的其他奇怪字符,如换行符,请选择另一个答案.

测试目录

ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt

进入目录的代码

find test -type d | while read f ; do
  echo "$f"
done

"$f"如果用作参数,则必须引用文件名().如果没有引号,则空格充当参数分隔符,并且为调用的命令提供多个参数.

并输出:

test/Baltimore
test/Cherry Hill
test/Edison
test/New York City
test/Philadelphia

如果你使用的是zsh,你也可以这样做:`alias duc ='du -sh*(/)'` (2认同)


Jonathan Lef.. 7

这在标准的Unix中非常棘手,并且大多数解决方案都会违反换行符或其他一些字符.但是,如果您使用的是GNU工具集,那么您可以利用该find选项-print0并使用xargs相应的选项-0(减零).有两个字符不能出现在简单的文件名中; 那些是斜线和NUL'\ 0'.显然,斜杠出现在路径名中,因此使用NUL'\ 0'标记名称末尾的GNU解决方案是巧妙且简单的.



1> Charles Duff..:

首先,不要这样做.最好的方法是find -exec正确使用:

# this is safe
find test -type d -exec echo '{}' +

另一种安全方法是使用NUL终止列表,但这需要您的查找支持-print0:

# this is safe
while IFS= read -r -d '' n; do
  printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d -print0)

您还可以从find填充数组,并稍后传递该数组:

# this is safe
declare -a myarray
while IFS= read -r -d '' n; do
  myarray+=( "$n" )
done < <(find test -mindepth 1 -type d -print0)
printf '%q\n' "${myarray[@]}" # printf is an example; use it however you want

如果您的查找不支持-print0,那么您的结果就是不安全的 - 如果文件中存在包含其名称中的换行符(以下是合法的),则以下内容将无法按预期运行:

# this is unsafe
while IFS= read -r n; do
  printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d)

如果不打算使用上述方法之一,第三种方法(在时间和内存使用方面效率较低,因为它在进行分词之前读取子进程的整个输出)是使用一个IFS变量不包含空格字符.关闭通配符(set -f),以防止含水珠字符,例如字符串[],*?从被扩展:

# this is unsafe (but less unsafe than it would be without the following precautions)
(
 IFS=$'\n' # split only on newlines
 set -f    # disable globbing
 for n in $(find test -mindepth 1 -type d); do
   printf '%q\n' "$n"
 done
)

最后,对于命令行参数的情况,如果你的shell支持它们,你应该使用数组(即它是ksh,bash或zsh):

# this is safe
for d in "$@"; do
  printf '%s\n' "$d"
done

将保持分离.请注意,引用(以及使用$@而不是$*)很重要.数组也可以用其他方式填充,例如glob表达式:

# this is safe
entries=( test/* )
for d in "${entries[@]}"; do
  printf '%s\n' "$d"
done


我以前从未见过$'\n'语法.这是如何运作的?(我原以为IFS ='\n'或IFS ="\n"都可以使用,但两者都没有.)

2> Johannes Sch..:
find . -type d | while read file; do echo $file; done

但是,如果文件名包含换行符,则不起作用.当你真正想要在变量中使用目录名时,以上是我所知道的唯一解决方案.如果您只想执行某些命令,请使用xargs.

find . -type d -print0 | xargs -0 echo 'The directory is: '


@Charles:对于大量文件,xargs效率更高:它只产生一个进程.-exec选项为每个文件分配一个新进程,速度可能慢一个数量级.
亚当,不,'+'会聚合尽可能多的文件名,然后执行.但它没有像并行运行这样的整洁功能:)
请注意,如果您想对文件名执行某些操作,则必须引用它们.例如:`找到.-type d | 同时读取文件; 做ls"$ file"; done`

3> cbliard..:

这是一个处理文件名中的制表符和/或空格的简单解决方案.如果你必须处理文件名中的其他奇怪字符,如换行符,请选择另一个答案.

测试目录

ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt

进入目录的代码

find test -type d | while read f ; do
  echo "$f"
done

"$f"如果用作参数,则必须引用文件名().如果没有引号,则空格充当参数分隔符,并且为调用的命令提供多个参数.

并输出:

test/Baltimore
test/Cherry Hill
test/Edison
test/New York City
test/Philadelphia


如果你使用的是zsh,你也可以这样做:`alias duc ='du -sh*(/)'`

4> Jonathan Lef..:

这在标准的Unix中非常棘手,并且大多数解决方案都会违反换行符或其他一些字符.但是,如果您使用的是GNU工具集,那么您可以利用该find选项-print0并使用xargs相应的选项-0(减零).有两个字符不能出现在简单的文件名中; 那些是斜线和NUL'\ 0'.显然,斜杠出现在路径名中,因此使用NUL'\ 0'标记名称末尾的GNU解决方案是巧妙且简单的.

推荐阅读
夏晶阳--艺术
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有