Bash for 循环,字符串 var 包含空格

Bash for 循环,字符串 var 包含空格

在我的目录中,我有两个带有空格的文件,foo bar以及another file.我还有两个没有空间的文件,file1以及file2.

以下脚本有效:

for f in foo\ bar another\ file; do file "$f"; done

这个脚本也有效:

for f in 'foo bar' 'another file'; do file "$f"; done

但以下脚本不起作用:

files="foo\ bar another\ file"
for f in $files; do file "$f"; done

甚至这个脚本也不起作用:

files="'foo bar' 'another file'"
for f in $files; do file "$f"; done

但是,如果文件不包含空格,该脚本将起作用:

files="file1 file2"
for f in $files; do file "$f"; done

谢谢!

编辑

我的脚本的代码片段:

while getopts "i:a:c:d:f:g:h" arg; do
  case $arg in
    i) files=$OPTARG;;
    # ...
  esac
done

for f in $files; do file "$f"; done

对于没有空格的文件,我的脚本可以工作。但我想通过以下方式之一运行脚本传递带有空格作为参数的文件:

./script.sh -i "foo\ bar another\ file"
./script.sh -i foo\ bar another\ file
./script.sh -i "'foo bar' 'another file'"
./script.sh -i 'foo bar' 'another file'

答案1

如果您正在使用,bash则可以为此使用数组

#!/bin/bash
files=('foo bar' 'another file' file1 'file2')
for f in "${files[@]}"; do file -- "$f"; done

包含空格或 shell 语法中的任何其他特殊字符(例如;|、等)的文件名需要加引号。&*

至少在列表上下文中的扩展周围也需要双引号(如"${files[@]}""$f"这里),对于包含通配符(*?[,以及\在某些版本中,如果启用的bash话更多)或字符(空格、制表符和换行符)的文件名默认)。extglob$IFS

对于不包含这些字符且不包含非 ASCII 字符的文件名,它是可选的(但我建议它)。

如果文件列表来自当前工作目录,您可以按照您的预期使用通配符,例如files=(*f*)匹配f名称中包含的任何文件或目录,尽管您希望shopt -s nullglob事先避免*f*在不匹配的情况下获取文字。 (但是你可能可以完全使用for f in *f*; do...done并避免使用数组)。

--的标记告诉file它任何后续参数都是文件名 - 即使它以破折号开头。

man bash使用(搜索Arrays)或仅阅读更多内容info bash arrays

答案2

您发布的四个脚本调用之间存在一些差异。

./script.sh -i "'foo bar' 'another file'"
./script.sh -i "foo\ bar another\ file"

上述两个参数都作为第一个参数传递给脚本-i,单个字符串作为第二个参数。在第一个中,它是'foo bar' 'another file',在第二个中,它是foo\ bar another\ file。在 shell 语言中,两者都是表示两个字符串(或文件名)foo baranother file.但是引号和反斜杠处理仅适用于字符串位于原始命令行上的情况,而不是当它们位于变量内部时适用,因为它们最终出现在字符串中。

./script.sh -i foo\ bar another\ file
./script.sh -i 'foo bar' 'another file'

另一方面,这两个总共通过了参数:-ifoo bar、 和another file

区别有些重要,因为安全地处理不同的参数要容易得多。您只需保持它们完整,而不必处理其中嵌入的引号和转义符。

另外,重要的是,运行类似的操作script ./*.txt会将文件名作为不同的参数传递。

例如,如果调用如下,则只会调用file两个文件script 'foo bar' another\ file

#! /bin/sh -
for f in "$@"; do
    file -- "$f"
done

或者,甚至更简单、更便携,for默认情况下循环位置参数:

#! /bin/sh -
for f do
    file -- "$f"
done

但你也有 getopts 。并且将文件名作为不同的参数,只有第一个会作为-i.在这里,基本上有两种常见的选择。

要么让用户-i重复使用该选项,将文件名收集到一个数组中,所以:

#! /bin/bash -
files=()
while getopts "i:" arg; do
  case $arg in
    (i) files+=("$OPTARG");;
  esac
done

for f in "${files[@]}"; do file -- "$f"; done

并运行为script -i "foo bar" -i "another file". (运行script -i file1 file2会被file2忽略。)类似地,您可以添加另一个数组来收集通过另一个选项给出的文件名。

或者,让选项设置脚本工作的“模式”,并将文件名作为与选项不同的列表。getopts保持所有参数完好无损,您只需删除它处理过的参数即可shift。所以:

#! /bin/bash -

unset -v mode
while getopts "ie" arg; do
  case $arg in
    (i) mode=i;;
    (e) mode=e;;
    (*) : handle usage error;;
  esac
done
shift "$((OPTIND - 1))"

case "$mode" in
  (i) for f do file -- "$f"; done;;
  (e) echo "do something else with the files";;
  (*) echo "error: mode not specified" >&2;;
esac

然后将其运行为script -i "foo bar" "another file"orscript -i -- -file-starting-with-dash- -other-file-script -i -- *

我在这里假设您除了getopts接受之外还做了其他事情-i,因为否则您可能会完全放弃该标志。 :) 但是您还有哪些其他选择,在某种程度上会影响最明智(或习惯)的解决方案是什么。

此外,如果您的循环仅调用file文件,您可以只运行file -- "${files[@]}"orfile -- "$@"并跳过循环。


但是,如果您希望能够做到这一点:

script -i foo bar -e doo daa

让脚本为文件foo和做一件事bar,为doo和 做另一件事daa,那么这是一个不同的问题。当然,这是可以做到的,但getopts可能不是实现这一目标的工具。

也可以看看:

而且当然:

针对尝试在单个变量中处理多个不同的任意字符串(文件名)的问题。

答案3

对于命令行解析,请将路径名操作数安排为始终是命令行上的最后一个:

./myscript -a -b -c -- 'foo bar' 'another file' file[12]

选项的解析看起来像

a_opt=false b_opt=false c_opt=false
while getopts abc opt; do
     case $opt in
         a) a_opt=true ;;
         b) b_opt=true ;;
         c) c_opt=true ;;
         *) echo error >&2; exit 1
    esac
done

shift "$(( OPTIND - 1 ))"

for pathname do
    # process pathname operand "$pathname" here
done

shift确保移出已处理的选项,以便路径名操作数是位置参数列表中唯一留下的内容。

如果这是不可能的,请允许-i多次指定该选项,并在每次在循环中遇到它时将给定的参数收集在数组中:

pathnames=() a_opt=false b_opt=false c_opt=false
while getopts abci: opt; do
     case $opt in
         a) a_opt=true ;;
         b) b_opt=true ;;
         c) c_opt=true ;;
         i) pathnames+=( "$OPTARG" ) ;;
         *) echo error >&2; exit 1
    esac
done

shift "$(( OPTIND - 1 ))"

for pathname in "${pathnames[@]}"; do
    # process pathname argument "$pathname" here
done

这将被称为

./myscript -a -b -c -i 'foo bar' -i 'another file' -i file1 -i file2

答案4

Bash 不太擅长处理带空格的变量。您必须使用“重命名”功能来帮助您完成此操作,如下所示:

如果您使用的是类似 Debian 的系统,请使用以下内容

sudo apt-get install rename

如果您使用的是类似红帽的系统,请使用以下内容

sudo yum install install rename

移动到您的文件所在的目录

cd your_target_directory

重命名所有包含空格字符的文件/变量。通过这一步,空间不再有问题

rename 's/ /_/g' *

从这里继续毫无问题...享受

相关内容