Bash:在空间上分开但不在逃逸的空间上

时间:2016-05-24 23:35:42

标签: bash split escaping whitespace filenames

我正在尝试编写一个读取用户输入的bash脚本(一些文件以便用户可以使用TAB完成)并将它们复制到特定的文件夹中。

#/bin/bash
read -e files
for file in $files
do
    echo $file
    cp "$file" folder/"$file"
done

可以:file1 file2 ...

或者使用:file*(即使文件夹中有空格的文件名)。

但是对于使用反斜杠\ 进行空间转义的文件名不起作用:file\ with\ space转义空格被忽略,字符串在每个空格上拆分,甚至转义。

我看到有关引用,printf,IFS,read和while的信息......我认为这是非常基本的bash脚本,但我找不到一个好的解决方案。你能救我吗?

4 个答案:

答案 0 :(得分:3)

在不加引号扩展之前清除IFS将允许在防止字符串拆分的同时进行通配:

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

请注意,我们在IFS=$' \t\n'操作期间强制执行默认read,这可确保在该阶段将未加引号的空格视为数组元素之间的分隔符。之后,与files=( $glob )相比,我们有IFS='',因此空白区域不再能够将个别名称分开。

答案 1 :(得分:1)

有一个功能齐全的文件和全球解决方案。

借助于使用xargs(能够保留引用的字符串)。但是你需要在引号内写入带空格的文件:

"file with spaces"

使用脚本时:取消引用读取并引用listOfFiles的作业。

我也在@CharlesDuffy(感谢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")

答案 2 :(得分:0)

您可以将文件名读入数组,然后遍历数组元素:

read -e -a files
for file in "${files[@]}"; do
    echo "$file"
    cp "$file" folder/"$file"
done

无论你如何引用,读入单个字符串都不起作用:字符串将在每个空格(未引用时)分开或根本不分开(引用时)。有关详细信息,请参阅this canonical Q&A(您的案例是列表中的最后一项)。

这可以防止通配,即file*未展开。有关将此考虑在内的解决方案,请参阅Charles' answer

答案 3 :(得分:0)

请注意,Charles Duffy和user2350426的答案都不会保留转义的*;他们也会扩展它们。

但是,本杰明的方法根本不会奏效。他弄错了,您可以先将glob放入字符串中,然后将它们加载到数组中。

然后它将按需要工作:

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变量,以便它们保持局部状态。