如何在 bash 循环列表中转义空白? [英] How can I escape white space in a bash loop list?
问题描述
我有一个 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屋!