在我的目录中,我有两个带有空格的文件,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 bar
和another file
.但是引号和反斜杠处理仅适用于字符串位于原始命令行上的情况,而不是当它们位于变量内部时适用,因为它们最终出现在字符串中。
./script.sh -i foo\ bar another\ file
./script.sh -i 'foo bar' 'another file'
另一方面,这两个总共通过了三参数:-i
、foo 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
可能不是实现这一目标的工具。
也可以看看:
- https://mywiki.wooledge.org/BashGuide/Arrays特别是对于数组
- 我们如何运行存储在变量中的命令?尽管标题是关于命令的,但它也讨论了数组。
而且当然:
针对尝试在单个变量中处理多个不同的任意字符串(文件名)的问题。
答案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' *
从这里继续毫无问题...享受