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

我有一个 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

樱桃山和纽约市被视为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
137421 次浏览

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

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

另一种安全的方法是使用以 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)

您还可以从 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变量。关闭 globbing (set -f) ,以防止包含 globbing 字符(如 []*?)的字符串被展开:

# 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

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

# this is safe
entries=( test/* )
for d in "${entries[@]}"; do
printf '%s\n' "$d"
done
find . -type d | while read file; do echo $file; done

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

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

这在标准 Unix 中非常棘手,而且大多数解决方案都与换行符或其他字符有冲突。但是,如果您正在使用 GNU 工具集,那么可以利用 find选项 -print0并使用 xargs和相应的选项 -0(负零)。有两个字符不能出现在简单的文件名中; 它们是斜杠和 NUL’0’。很明显,斜杠出现在路径名中,因此使用 NUL’0’标记名称末尾的 GNU 解决方案是非常巧妙和简单的。

为了补充 乔纳森所说的: 对 findxargs使用 -print0选项如下:

find test/* -type d -print0 | xargs -0 command

它将执行带有正确参数的命令 command; 带有空格的目录将被正确引用(也就是说,它们将作为一个参数传入)。

不要将列表存储为字符串; 将它们存储为数组以避免所有这些分隔符混淆。下面是一个示例脚本,它可以对 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

现在让我们在一个测试目录中尝试一下,其中包含一两条曲线:

$ 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

刚发现我的 有个问题和你的有些相似之处。显然,如果您想将参数传递到命令中

test.sh "Cherry Hill" "New York City"

按顺序打印出来

for SOME_ARG in "$@"
do
echo "$SOME_ARG";
done;

注意 $@ 被双引号包围,一些注释 给你

我最后使用了递归和 for item in /path/*:

function recursedir {
local item
for item in "${1%/}"/*
do
if [ -d "$item" ]
then
recursedir "$item"
else
command
fi
done
}

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

测试目录

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

只有一个简单的变体问题... 转换文件类型。 flv 到。 mp3(呵呵)。

for file in read `find . *.flv`; do ffmpeg -i ${file} -acodec copy ${file}.mp3;done

递归地找到所有的 Macintosh 用户 flash 文件,并将它们转换成音频(复制,无转码) ... ... 就像上面的 while 一样,注意到 read 而不仅仅是‘ for file in ’将会转义。

#!/bin/bash


dirtys=()


for folder in *
do
if [ -d "$folder" ]; then
dirtys=("${dirtys[@]}" "$folder")
fi
done


for dir in "${dirtys[@]}"
do
for file in "$dir"/\*.mov   # <== *.mov
do
#dir_e=`echo "$dir" | sed 's/[[:space:]]/\\\ /g'`   -- This line will replace each space into '\ '
out=`echo "$file" | sed 's/\(.*\)\/\(.*\)/\2/'`     # These two line code can be written in one line using multiple sed commands.
out=`echo "$out" | sed 's/[[:space:]]/_/g'`
#echo "ffmpeg -i $out_e -sameq -vcodec msmpeg4v2 -acodec pcm_u8 $dir_e/${out/%mov/avi}"
`ffmpeg -i "$file" -sameq -vcodec msmpeg4v2 -acodec pcm_u8 "$dir"/${out/%mov/avi}`
done
done

上面的代码将.mov 文件转换为.avi 文件夹名也有 留白。我的上面的脚本将转换。电影档案。Avi 文件在同一文件夹本身。我不知道这对你们有没有帮助。

案例:

[sony@localhost shell_tutorial]$ ls
Chapter 01 - Introduction  Chapter 02 - Your First Shell Script
[sony@localhost shell_tutorial]$ cd Chapter\ 01\ -\ Introduction/
[sony@localhost Chapter 01 - Introduction]$ ls
0101 - About this Course.mov   0102 - Course Structure.mov
[sony@localhost Chapter 01 - Introduction]$ ./above_script
... successfully executed.
[sony@localhost Chapter 01 - Introduction]$ ls
0101_-_About_this_Course.avi  0102_-_Course_Structure.avi
0101 - About this Course.mov  0102 - Course Structure.mov
[sony@localhost Chapter 01 - Introduction]$ CHEERS!

干杯!

对我来说,这很有效,而且非常“干净”:

for f in "$(find ./test -type d)" ; do
echo "$f"
done

为什么不直接放呢

IFS='\n'

这会将字段分隔符从 < Space > < Tab > < Newline > 更改为仅仅 < Newline >

find . -print0|while read -d $'\0' file; do echo "$file"; done

我吸毒

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in $( find "$1" -type d ! -path "$1" )
do
echo $f
done
IFS=$SAVEIFS

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

将文件列表转换为 Bash 数组。这使用了 Matt McClure 从 Bash 函数返回数组的方法: Http://notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html 结果是一种将任何多行输入转换为 Bash 数组的方法。

#!/bin/bash


# This is the command where we want to convert the output to an array.
# Output is: fileSize fileNameIncludingPath
multiLineCommand="find . -mindepth 1 -printf '%s %p\\n'"


# This eval converts the multi-line output of multiLineCommand to a
# Bash array. To convert stdin, remove: < <(eval "$multiLineCommand" )
eval "declare -a myArray=`( arr=(); while read -r line; do arr[${#arr[@]}]="$line"; done; declare -p arr | sed -e 's/^declare -a arr=//' ) < <(eval "$multiLineCommand" )`"


for f in "${myArray[@]}"
do
echo "Element: $f"
done

这种方法在出现错误字符时似乎仍然有效,并且是将任何输入转换为 Bash 数组的通用方法。缺点是如果输入很长,您可能会超过 Bash 的命令行大小限制,或者占用大量内存。

最终在列表上工作的循环也有管道输入列表的方法有一个缺点,即读取 stdin 不容易(比如要求用户输入) ,而且循环是一个新的过程,所以你可能想知道为什么你在循环中设置的变量在循环结束后不可用。

我也不喜欢设置 IFS,它可以搞乱其他代码。

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

read artist;


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

我需要相同的概念来连续压缩某个文件夹中的多个目录或文件。我已经解决了使用 awk 解析 ls 中的列表并避免名称中空白的问题。

source="/xxx/xxx"
dest="/yyy/yyy"


n_max=`ls . | wc -l`


echo "Loop over items..."
i=1
while [ $i -le $n_max ];do
item=`ls . | awk 'NR=='$i'' `
echo "File selected for compression: $item"
tar -cvzf $dest/"$item".tar.gz "$item"
i=$(( i + 1 ))
done
echo "Done!!!"

你觉得怎么样?

find Downloads -type f | while read file; do printf "%q\n" "$file"; done

您可以使用 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

< ! >

我看到了太多复杂的答案。我不想传递 find 实用程序的输出或者编写一个循环,因为 find 对此有“ exec”选项。

我的问题是,我想把所有扩展名为 dbf 的文件移动到当前文件夹中,其中一些文件包含空白。

我是这样处理的:

 find . -name \*.dbf -print0 -exec mv '{}'  . ';'

对我来说很简单