Bash:拆分 space 但不转义 space
Bash: split on space but not on escaped space
我正在尝试编写一个 bash 脚本来读取用户的输入(一些文件以便用户可以使用 TAB
完成)并将它们复制到特定文件夹中。
#/bin/bash
read -e files
for file in $files
do
echo $file
cp "$file" folder/"$file"
done
适合:file1 file2 ...
或用:file*
(即使文件夹中有一个文件名带有space)。
但它不适用于 space 用反斜杠 \
转义的文件名,例如: file\ with\ space
转义的 spaces 被忽略并且字符串在每个 spaces,竟然逃脱了。
我看到了有关 quoting、printf、IFS、read 和 while 的信息...我认为这是非常基本的 bash 脚本,但我找不到好的解决方案。你能帮帮我吗?
您可以将文件名读入数组,然后遍历数组元素:
read -e -a files
for file in "${files[@]}"; do
echo "$file"
cp "$file" folder/"$file"
done
无论您如何引用,都无法读取单个字符串:字符串将在每个 space 处拆分(未引用时)或根本不拆分(引用时)。有关详细信息,请参阅 this canonical Q&A(您的案例是列表中的最后一项)。
这可以防止通配,即 file*
不会展开。有关考虑到这一点的解决方案,请参阅 。
在未加引号的扩展之前清除 IFS
将允许 globbing 继续进行,同时防止字符串拆分:
IFS=$' \t\n' read -e -a globs # read glob expressions into an array
IFS=''
for glob in "${globs[@]}"; do # these aren't filenames; don't claim that they are.
files=( $glob ) # expand the glob into filenames
# detect the case where no files matched by checking whether the first result exists
# these *would* need to be quoted, but [[ ]] turns off string-splitting and globbing
[[ -e $files || -L $files ]] || {
printf 'ERROR: Glob expression %q did not match any files!\n' "$glob" >&2
continue
}
printf '%q\n' "${files[@]}" # print one line per file matching
cp -- "${files[@]}" folder/ # copy those files to the target
done
请注意,我们在 read
操作期间强制执行默认值 IFS=$' \t\n'
,这可确保在该阶段将未加引号的空格视为数组元素之间的分隔符。后来,有了 files=( $glob )
,相比之下,我们有了 IFS=''
,所以空格不再能将单个名称分开。
文件和 glob 有一个功能齐全的解决方案。
借助 xargs(能够保留带引号的字符串)。但是你需要写在引号内有空格的文件:
"file with spaces"
当您使用脚本时:取消引用阅读并引用 listOfFiles
的作业。
我也在利用 @CharlesDuffy 的 post 的一些想法(感谢 Charles)。
#!/bin/bash
# read -e listOfFiles
listOfFiles='file1 file* "file with spaces"'
IFS=''
while IFS='' read glob; do # read each file expressions into an array
files=( $glob ) # try to expand the glob into filenames
# If no file match the split glob
# Then assume that the glob is a file and test its existence
[[ -e $files || -L $files ]] || {
files="$glob"
[[ -e $files || -L $files ]] || {
printf 'ERROR: Glob "%q" did not match any file!\n' "$glob" >&2
continue
}
}
printf '%q\n' "${files[@]}" # print one line per file matching
cp -- "${files[@]}" folder/ # copy those files to the target
done < <(xargs -n1 <<<"$listOfFiles")
请注意,Charles Duffy 和 user2350426 的答案都没有保留转义的 *
;他们也会扩展它们。
然而,Benjamin 的方法根本不会进行 globbing。他错了,你可以先把你的 globs 放在一个字符串中,然后将它们加载到一个数组中。
然后它将按预期工作:
globs='file1 file\ 2 file-* file\* file\"\"' # or read -re here
# Do splitting and globbing:
shopt -s nullglob
eval "files=( $globs )"
shopt -u nullglob
# Now we can use ${files[@]}:
for file in "${files[@]}"; do
printf "%s\n" "$file"
done
另请注意使用 nullglob
忽略不可扩展的 glob。
您可能还想使用 failglob
或者,为了更细粒度的控制,使用上述答案中的代码。
在函数内部,您可能想要 declare
变量,以便它们保持在本地。
我正在尝试编写一个 bash 脚本来读取用户的输入(一些文件以便用户可以使用 TAB
完成)并将它们复制到特定文件夹中。
#/bin/bash
read -e files
for file in $files
do
echo $file
cp "$file" folder/"$file"
done
适合:file1 file2 ...
或用:file*
(即使文件夹中有一个文件名带有space)。
但它不适用于 space 用反斜杠 \
转义的文件名,例如: file\ with\ space
转义的 spaces 被忽略并且字符串在每个 spaces,竟然逃脱了。
我看到了有关 quoting、printf、IFS、read 和 while 的信息...我认为这是非常基本的 bash 脚本,但我找不到好的解决方案。你能帮帮我吗?
您可以将文件名读入数组,然后遍历数组元素:
read -e -a files
for file in "${files[@]}"; do
echo "$file"
cp "$file" folder/"$file"
done
无论您如何引用,都无法读取单个字符串:字符串将在每个 space 处拆分(未引用时)或根本不拆分(引用时)。有关详细信息,请参阅 this canonical Q&A(您的案例是列表中的最后一项)。
这可以防止通配,即 file*
不会展开。有关考虑到这一点的解决方案,请参阅
在未加引号的扩展之前清除 IFS
将允许 globbing 继续进行,同时防止字符串拆分:
IFS=$' \t\n' read -e -a globs # read glob expressions into an array
IFS=''
for glob in "${globs[@]}"; do # these aren't filenames; don't claim that they are.
files=( $glob ) # expand the glob into filenames
# detect the case where no files matched by checking whether the first result exists
# these *would* need to be quoted, but [[ ]] turns off string-splitting and globbing
[[ -e $files || -L $files ]] || {
printf 'ERROR: Glob expression %q did not match any files!\n' "$glob" >&2
continue
}
printf '%q\n' "${files[@]}" # print one line per file matching
cp -- "${files[@]}" folder/ # copy those files to the target
done
请注意,我们在 read
操作期间强制执行默认值 IFS=$' \t\n'
,这可确保在该阶段将未加引号的空格视为数组元素之间的分隔符。后来,有了 files=( $glob )
,相比之下,我们有了 IFS=''
,所以空格不再能将单个名称分开。
文件和 glob 有一个功能齐全的解决方案。
借助 xargs(能够保留带引号的字符串)。但是你需要写在引号内有空格的文件:
"file with spaces"
当您使用脚本时:取消引用阅读并引用 listOfFiles
的作业。
我也在利用 @CharlesDuffy 的 post 的一些想法(感谢 Charles)。
#!/bin/bash
# read -e listOfFiles
listOfFiles='file1 file* "file with spaces"'
IFS=''
while IFS='' read glob; do # read each file expressions into an array
files=( $glob ) # try to expand the glob into filenames
# If no file match the split glob
# Then assume that the glob is a file and test its existence
[[ -e $files || -L $files ]] || {
files="$glob"
[[ -e $files || -L $files ]] || {
printf 'ERROR: Glob "%q" did not match any file!\n' "$glob" >&2
continue
}
}
printf '%q\n' "${files[@]}" # print one line per file matching
cp -- "${files[@]}" folder/ # copy those files to the target
done < <(xargs -n1 <<<"$listOfFiles")
请注意,Charles Duffy 和 user2350426 的答案都没有保留转义的 *
;他们也会扩展它们。
然而,Benjamin 的方法根本不会进行 globbing。他错了,你可以先把你的 globs 放在一个字符串中,然后将它们加载到一个数组中。
然后它将按预期工作:
globs='file1 file\ 2 file-* file\* file\"\"' # or read -re here
# Do splitting and globbing:
shopt -s nullglob
eval "files=( $globs )"
shopt -u nullglob
# Now we can use ${files[@]}:
for file in "${files[@]}"; do
printf "%s\n" "$file"
done
另请注意使用 nullglob
忽略不可扩展的 glob。
您可能还想使用 failglob
或者,为了更细粒度的控制,使用上述答案中的代码。
在函数内部,您可能想要 declare
变量,以便它们保持在本地。