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

MCS*_*MCS 117 bash whitespace loops

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

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

$ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt
Run Code Online (Sandbox Code Playgroud)

以及遍历目录的代码:

for f in `find test/* -type d`; do
  echo $f
done
Run Code Online (Sandbox Code Playgroud)

这是输出:

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
Run Code Online (Sandbox Code Playgroud)

但无济于事.

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


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

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

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

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

Cherry
Hill
New
York
City

Cha*_*ffy 103

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

# this is safe
find test -type d -exec echo '{}' +
Run Code Online (Sandbox Code Playgroud)

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

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

您还可以从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
Run Code Online (Sandbox Code Playgroud)

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

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

如果不打算使用上述方法之一,第三种方法(在时间和内存使用方面效率较低,因为它在进行分词之前读取子进程的整个输出)是使用一个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
)
Run Code Online (Sandbox Code Playgroud)

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

# this is safe
for d in "$@"; do
  printf '%s\n' "$d"
done
Run Code Online (Sandbox Code Playgroud)

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

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

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

Joh*_*itb 26

find . -type d | while read file; do echo $file; done
Run Code Online (Sandbox Code Playgroud)

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

find . -type d -print0 | xargs -0 echo 'The directory is: '
Run Code Online (Sandbox Code Playgroud)

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

cbl*_*ard 22

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

测试目录

ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt
Run Code Online (Sandbox Code Playgroud)

进入目录的代码

find test -type d | while read f ; do
  echo "$f"
done
Run Code Online (Sandbox Code Playgroud)

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

并输出:

test/Baltimore
test/Cherry Hill
test/Edison
test/New York City
test/Philadelphia
Run Code Online (Sandbox Code Playgroud)

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

Jon*_*ler 7

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


ama*_*ere 7

您可以暂时使用 IFS(内部字段分隔符):

OLD_IFS=$IFS     # Stores Default IFS
IFS=$'\n'        # Set it to line break
for f in `find test/* -type d`; do
    echo $f
done

IFS=$OLD_IFS
Run Code Online (Sandbox Code Playgroud)

<!>