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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/301039/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 17:47:44  来源:igfitidea点击:

How can I escape white space in a bash loop list?

bashloopswhitespace

提问by MCS

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.

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

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.

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

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:

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

dirlist="${@}"
(
  [[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'\n'
  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:

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

Cherry
Hill
New
York
City

采纳答案by Charles Duffy

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

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

# 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:

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

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

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

您还可以从 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

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):

如果您的 find 不支持-print0,则您的结果是不安全的——如果存在名称中包含换行符的文件(是的,这是合法的),则以下行为将不会按预期运行:

# this is unsafe
while IFS= read -r n; do
  printf '%q\n' "$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 IFSvariable which doesn't contain the space character. Turn off globbing (set -f) to prevent strings containing glob characters such as [], *or ?from being expanded:

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

# 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
)

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):

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

# this is safe
for d in "$@"; do
  printf '%s\n' "$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:

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

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

回答by Johannes Schaub - litb

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

However, doesn't work if the file-name contains newlines. The above is the only solution i know of when you actually want to have the directory name in a variable. If you just want to execute some command, use xargs.

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

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

回答by cbliard

Here is a simple solution which handles tabs and/or whitespaces in the filename. If you have to deal with other strange characters in the filename like newlines, pick another answer.

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

The test directory

测试目录

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

The code to go into the directories

进入目录的代码

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

The filename must be quoted ("$f") if used as argument. Without quotes, the spaces act as argument separator and multiple arguments are given to the invoked command.

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

And the output:

和输出:

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

回答by Jonathan Leffler

This is exceedingly tricky in standard Unix, and most solutions run foul of newlines or some other character. However, if you are using the GNU tool set, then you can exploit the findoption -print0and use xargswith the corresponding option -0(minus-zero). There are two characters that cannot appear in a simple filename; those are slash and NUL '\0'. Obviously, slash appears in pathnames, so the GNU solution of using a NUL '\0' to mark the end of the name is ingenious and fool-proof.

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

回答by Gordon Davisson

Don't store lists as strings; store them as arrays to avoid all this delimiter confusion. Here's an example script that'll either operate on all subdirectories of test, or the list supplied on its command line:

不要将列表存储为字符串;将它们存储为数组以避免所有这些分隔符混淆。这是一个示例脚本,它将在 test 的所有子目录或其命令行上提供的列表上运行:

#!/bin/bash
if [ $# -eq 0 ]; then
        # if no args supplies, build a list of subdirs of test/
        dirlist=() # start with empty list
        for f in test/*; do # for each item in test/ ...
                if [ -d "$f" ]; then # if it's a subdir...
                        dirlist=("${dirlist[@]}" "$f") # add it to the list
                fi
        done
else
        # if args were supplied, copy the list of args into dirlist
        dirlist=("$@")
fi
# now loop through dirlist, operating on each one
for dir in "${dirlist[@]}"; do
        printf "Directory: %s\n" "$dir"
done

Now let's try this out on a test directory with a curve or two thrown in:

现在让我们在带有一两条曲线的测试目录上试试这个:

$ ls -F test
Baltimore/
Cherry Hill/
Edison/
New York City/
Philadelphia/
this is a dirname with quotes, lfs, escapes: "\''?'?\e\n\d/
this is a file, not a directory
$ ./test.sh 
Directory: test/Baltimore
Directory: test/Cherry Hill
Directory: test/Edison
Directory: test/New York City
Directory: test/Philadelphia
Directory: test/this is a dirname with quotes, lfs, escapes: "\''
'
\e\n\d
$ ./test.sh "Cherry Hill" "New York City"
Directory: Cherry Hill
Directory: New York City

回答by oshunluvr

Why not just put

为什么不放

IFS='\n'

in front of the for command? This changes the field separator from < Space>< Tab>< Newline> to just < Newline>

在 for 命令前面?这会将字段分隔符从 <Space><Tab><Newline> 更改为 <Newline>

回答by Freakus

find . -print0|while read -d $'
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in $( find "" -type d ! -path "" )
do
  echo $f
done
IFS=$SAVEIFS
' file; do echo "$file"; done

回答by murpel

I use

我用

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

Wouldn't that be enough?
Idea taken from http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html

这还不够吗?
想法取自http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html

回答by amazingthere

You could use IFS (internal field separator) temporally using :

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

read artist;

find "/mnt/2tb_USB_hard_disc/p_music/$artist" -type f -name *.mp3 -exec mpg123 '{}' \;

回答by hardbutnot

ps if it is only about space in the input, then some double quotes worked smoothly for me...

ps 如果它只是关于输入中的空间,那么一些双引号对我来说很顺利......

##代码##