The*_*ema 838 bash command-line
我有一个复杂的命令,我想制作一个shell/bash脚本.我可以$1轻松地写出来:
foo $1 args -o $1.ext
Run Code Online (Sandbox Code Playgroud)
我希望能够将多个输入名称传递给脚本.什么是正确的方法呢?
当然,我想处理其中包含空格的文件名.
Rob*_*ble 1373
使用"$@"来代表所有的参数:
for var in "$@"
do
echo "$var"
done
Run Code Online (Sandbox Code Playgroud)
这将迭代每个参数并在单独的行上打印出来.$ @的行为类似于$*,但是当引用时,如果参数中有空格,则它们会被正确分解:
sh test.sh 1 2 '3 4'
1
2
3 4
Run Code Online (Sandbox Code Playgroud)
Jon*_*ler 227
Robert Gamble的简洁回答直接涉及这个问题.这个放大了包含空格的文件名的一些问题.
基本论点: "$@"是正确的,$*(未引用)几乎总是错误的.这是因为"$@"当参数包含空格时工作正常,并且与$*它们不包含空格时的工作方式相同.在某些情况下,"$*"也可以,但"$@"通常(但不总是)在同一个地方工作.未加引号,$@并且$*是等价的(而且几乎总是错的).
那么,是什么样的区别$*,$@,"$*",和"$@"?它们都与'shell的所有参数'有关,但它们做了不同的事情.什么时候不引用,$*并$@做同样的事情.他们将每个'单词'(非空白的序列)视为一个单独的参数.引用的表单完全不同,但是:"$*"将参数列表视为单个空格分隔的字符串,而"$@"对参数的处理几乎与在命令行中指定时完全相同.
"$@"当没有位置参数时,它会扩展到任何东西; "$*"扩展为一个空字符串 - 是的,这是有区别的,虽然很难察觉它.在引入(非标准)命令后,请参阅下面的更多信息al.
次要论文:如果你需要用空格处理参数然后将它们传递给其他命令,那么你有时需要非标准的工具来帮助.(或者你应该谨慎使用数组:"${array[@]}"行为类似于"$@".)
例:
$ mkdir "my dir" anotherdir
$ ls
anotherdir my dir
$ cp /dev/null "my dir/my file"
$ cp /dev/null "anotherdir/myfile"
$ ls -Fltr
total 0
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir/
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir/
$ ls -Fltr *
my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$ ls -Fltr "./my dir" "./anotherdir"
./my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
./anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$ var='"./my dir" "./anotherdir"' && echo $var
"./my dir" "./anotherdir"
$ ls -Fltr $var
ls: "./anotherdir": No such file or directory
ls: "./my: No such file or directory
ls: dir": No such file or directory
$
Run Code Online (Sandbox Code Playgroud)
为什么不起作用?它不起作用,因为shell在扩展变量之前处理引号.所以,要让shell注意嵌入的引号$var,你必须使用eval:
$ eval ls -Fltr $var
./my dir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file
./anotherdir:
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile
$
Run Code Online (Sandbox Code Playgroud)
当你有文件名如" He said,
"Don't do this!""(带引号和双引号和空格)时,这会变得非常棘手.
$ cp /dev/null "He said, \"Don't do this!\""
$ ls
He said, "Don't do this!" anotherdir my dir
$ ls -l
total 0
-rw-r--r-- 1 jleffler staff 0 Nov 1 15:54 He said, "Don't do this!"
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir
drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir
$
Run Code Online (Sandbox Code Playgroud)
shell(所有这些)都不会使这些东西特别容易处理,所以(有趣的是)很多Unix程序都不能很好地处理它们.在Unix上,文件名(单个组件)可以包含除斜杠和NUL之外的任何字符'\0'.但是,shell强烈建议路径名中的任何位置都不要有空格或换行符或制表符.这也是标准Unix文件名不包含空格等的原因.
处理可能包含空格和其他麻烦字符的文件名时,你必须非常小心,我很久以前就发现我需要一个在Unix上不标准的程序.我称之为escape(版本1.1的日期为1989-08-23T16:01:45Z).
这是一个escape使用的例子- 使用SCCS控制系统.这是一个封面脚本,同时执行delta(思考签到)和
get(认为签出).各种论点,特别是-y(你进行更改的原因)将包含空格和换行符.请注意,该脚本的日期是1992年,因此它使用反向$(cmd ...)标记而不是
符号,并且不在#!/bin/sh第一行使用.
: "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
# Delta and get files
# Uses escape to allow for all weird combinations of quotes in arguments
case `basename $0 .sh` in
deledit) eflag="-e";;
esac
sflag="-s"
for arg in "$@"
do
case "$arg" in
-r*) gargs="$gargs `escape \"$arg\"`"
dargs="$dargs `escape \"$arg\"`"
;;
-e) gargs="$gargs `escape \"$arg\"`"
sflag=""
eflag=""
;;
-*) dargs="$dargs `escape \"$arg\"`"
;;
*) gargs="$gargs `escape \"$arg\"`"
dargs="$dargs `escape \"$arg\"`"
;;
esac
done
eval delta "$dargs" && eval get $eflag $sflag "$gargs"
Run Code Online (Sandbox Code Playgroud)
(这些天我可能不会非常彻底地使用escape - -e例如,参数不需要它- 但总的来说,这是我使用的更简单的脚本之一escape.)
该escape程序只是输出它的参数,而不是像echo
它一样,但它确保参数受到保护以供使用
eval(一级eval;我确实有一个程序执行远程shell执行,并且需要转义输出escape).
$ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
Run Code Online (Sandbox Code Playgroud)
我有另一个程序叫做al每行一个列出它的参数(它更古老:版本1.1日期1987-01-27T14:35:49).它在调试脚本时最有用,因为它可以插入命令行以查看实际传递给命令的参数.
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
Run Code Online (Sandbox Code Playgroud)
[ 补充:
现在为了显示各种"$@"符号之间的区别,这里还有一个例子:
$ cat xx.sh
set -x
al $@
al $*
al "$*"
al "$@"
$ sh xx.sh * */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$
Run Code Online (Sandbox Code Playgroud)
请注意,没有任何内容保留命令行*和*/*命令行之间的原始空白.另请注意,您可以使用以下命令更改shell中的"命令行参数":
set -- -new -opt and "arg with space"
Run Code Online (Sandbox Code Playgroud)
这设置了4个选项,' -new',' -opt',' and'和' arg with space'.
]
嗯,这是一个相当长的答案 - 也许解释是更好的术语.escape可根据要求提供的源代码(通过电子邮件发送到gmail dot com的firstname dot lastname).源代码al非常简单:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
Run Code Online (Sandbox Code Playgroud)
就这样.它相当于test.shRobert Gamble所展示的脚本,并且可以编写为shell函数(但是当我第一次编写时,本地版本的Bourne shell中不存在shell函数al).
另请注意,您可以编写al为简单的shell脚本:
[ $# != 0 ] && printf "%s\n" "$@"
Run Code Online (Sandbox Code Playgroud)
需要条件,以便在不传递参数时不产生输出.该printf命令将生成一个只有格式字符串参数的空行,但C程序不生成任何内容.
Alo*_*hal 126
请注意,罗伯特的答案是正确的,它也适用sh.您可以(便携地)进一步简化它:
for i in "$@"
Run Code Online (Sandbox Code Playgroud)
相当于:
for i
Run Code Online (Sandbox Code Playgroud)
即,你什么都不需要!
测试($是命令提示符):
$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "$@"; do echo "$i"; done
a
b
spaces here
d
Run Code Online (Sandbox Code Playgroud)
我首先在Kernighan和Pike的Unix编程环境中读到过这个.
在bash,help for文件中:
for NAME [in WORDS ... ;] do COMMANDS; done如果
'in WORDS ...;'不存在,则'in "$@"'假定.
nuo*_*eri 56
对于简单的情况,您也可以使用shift.它将参数列表视为队列,每个shift抛出第一个参数,剩下的每个参数的数量递减.
#this prints all arguments
while test $# -gt 0
do
echo "$1"
shift
done
Run Code Online (Sandbox Code Playgroud)
Ice*_*erg 27
针对参数变量 $# 进行循环也有效。
#! /bin/bash
for ((i=1; i<=$#; i++))
do
printf "${!i}\n"
done
Run Code Online (Sandbox Code Playgroud)
./test.sh 1 2 '3 4'
Run Code Online (Sandbox Code Playgroud)
输出:
1
2
3 4
Run Code Online (Sandbox Code Playgroud)
baz*_*baz 13
您还可以将它们作为数组元素进行访问,例如,如果您不想迭代所有这些元素
argc=$#
argv=("$@")
for (( j=0; j<argc; j++ )); do
echo "${argv[j]}"
done
Run Code Online (Sandbox Code Playgroud)
放大 baz 的答案,如果您需要使用索引枚举参数列表(例如搜索特定单词),您可以在不复制列表或改变它的情况下执行此操作。
假设您想在双破折号(“--”)处分割参数列表,并将破折号之前的参数传递给一个命令,将破折号之后的参数传递给另一个命令:
toolwrapper() {
for i in $(seq 1 $#); do
[[ "${!i}" == "--" ]] && break
done || return $? # returns error status if we don't "break"
echo "dashes at $i"
echo "Before dashes: ${@:1:i-1}"
echo "After dashes: ${@:i+1:$#}"
}
Run Code Online (Sandbox Code Playgroud)
结果应如下所示:
$ toolwrapper args for first tool -- and these are for the second
dashes at 5
Before dashes: args for first tool
After dashes: and these are for the second
Run Code Online (Sandbox Code Playgroud)
aparse() {
while [[ $# > 0 ]] ; do
case "$1" in
--arg1)
varg1=${2}
shift
;;
--arg2)
varg2=true
;;
esac
shift
done
}
aparse "$@"
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
583877 次 |
| 最近记录: |