如何在 bash 循环列表中转义空白? [英] How can I escape white space in a bash loop list?

查看:22
本文介绍了如何在 bash 循环列表中转义空白?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

这是我的测试目录的内容:

$ls -F 测试巴尔的摩/樱桃山/爱迪生/纽约市/费城/citys.txt

以及遍历目录的代码:

for f in `find test/* -type d`;做回声 $f完毕

输出如下:

<前>测试/巴尔的摩测试/樱桃爬坡道测试/爱迪生测试/新约克城市测试/费城

樱桃山和纽约市被视为 2 或 3 个单独的条目.

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

for f in `find test/* -type d |sed -e 's/^/"/' | sed -e 's/$/"/'`;做回声 $f完毕

但无济于事.

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

<小时>

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

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

dirlist="${@}"([[ -z "$dirlist" ]] &&dirlist=`find test -mindepth 1 -type d` &&IFS=$'
'对于 $dirlist 中的 d;做回声 $d完毕)

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

<前>樱桃爬坡道新的约克城市

解决方案

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

# 这是安全的find test -type d -exec echo '{}' +

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

# 这是安全的而 IFS= read -r -d '' n;做printf '%q
' "$n"完成<<(find test -mindepth 1 -type d -print0)

您也可以从 find 填充一个数组,然后再传递该数组:

# 这是安全的声明 -a myarray而 IFS= read -r -d '' n;做myarray+=( "$n" )完成<<(find test -mindepth 1 -type d -print0)printf '%q
' "${myarray[@]}" # printf 是一个例子;随心所欲地使用它

如果您的发现不支持 -print0,那么您的结果是不安全的——如果存在名称中包含换行符的文件(是的,这是合法的),则下面的行为将不符合预期:

# 这是不安全的而 IFS= 读 -r n;做printf '%q
' "$n"完成<<(find test -mindepth 1 -type d)

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

# 这是不安全的(但比没有以下预防措施的情况更不安全)(IFS=$'
' # 仅在换行符处拆分set -f # 禁用通配对于 $(find test -mindepth 1 -type d) 中的 n;做printf '%q
' "$n"完毕)

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

# 这是安全的对于$@"中的 d;做printf '%s
' "$d"完毕

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

# 这是安全的条目=(测试/*)对于${entries[@]}"中的 d;做printf '%s
' "$d"完毕

I have a bash shell script that loops through all child directories (but not files) of a certain directory. The problem is that some of the directory names contain spaces.

Here are the contents of my test directory:

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

And the code that loops through the directories:

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

Here's the output:

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

Cherry Hill and New York City are treated as 2 or 3 separate entries.

I tried quoting the filenames, like so:

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

but to no avail.

There's got to be a simple way to do this.


The answers below are great. But to make this more complicated - I don't always want to use the directories listed in my test directory. Sometimes I want to pass in the directory names as command-line parameters instead.

I took Charles' suggestion of setting the IFS and came up with the following:

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

and this works just fine unless there are spaces in the command line arguments (even if those arguments are quoted). For example, calling the script like this: test.sh "Cherry Hill" "New York City" produces the following output:

Cherry
Hill
New
York
City

解决方案

First, don't do it that way. The best approach is to use find -exec properly:

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

The other safe approach is to use NUL-terminated list, though this requires that your find support -print0:

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

You can also populate an array from find, and pass that array later:

# 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
' "${myarray[@]}" # printf is an example; use it however you want

If your find doesn't support -print0, your result is then unsafe -- the below will not behave as desired if files exist containing newlines in their names (which, yes, is legal):

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

If one isn't going to use one of the above, a third approach (less efficient in terms of both time and memory usage, as it reads the entire output of the subprocess before doing word-splitting) is to use an IFS variable which doesn't contain the space character. Turn off globbing (set -f) to prevent strings containing glob characters such as [], * or ? from being expanded:

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

Finally, for the command-line parameter case, you should be using arrays if your shell supports them (i.e. it's ksh, bash or zsh):

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

will maintain separation. Note that the quoting (and the use of $@ rather than $*) is important. Arrays can be populated in other ways as well, such as glob expressions:

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

这篇关于如何在 bash 循环列表中转义空白?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆