Shell里怎么输出指定的数字序列:
for i in {1..5}; do echo $i; done可以输出
1 2 3 4 5但是如果
END=5 for i in {1..$END}; do echo $i; done就不灵了。。。
怎么才能通过变量传一个区间进来,让他输出数字序列?
查了下文档,知道为什么{1..$END}
没有效果了,看GNU的bash手册是这么说的:
Brace expansion is erformed before any other expansions
and any characters special to other expansions are preserved in the
result. It is strictly textual. Bash does not apply any syntactic
interpretation to the context of the expansion or the text between the
braces. To avoid conflicts with parameter expansion, the string ‘${’ is
not considered eligible for brace expansion.
也就是说Brace expansion
是在其它所有类型的展开之前处理的包括参数和变量展开,因此{1..$END}
就是{1..$END}
,原封不动,不知道你能不能理解?或许你将{1..$END}
看成{1..END}
好了,因为这里$END
只是一个字符串而已。
另外一方面,GNU的bash手册也说明了这个语法的使用方法:
A sequence expression takes the form {x..y[..incr]}, where x and y are
either integers or single characters, and incr, an optional increment,
is an integer. When integers are supplied, the expression expands to
each number between x and y, inclusive….When characters are supplied,
the expression expands to each character lexicographically between x and
y, inclusive. Note that both x and y must be of the same type. When the
increment is supplied, it is used as the difference between each term.
The default increment is 1 or -1 as appropriate.
从上面可以看出{x..y}
的限制条件是:1) 整数或者单个字符; 2)两者要是同一个类型。回到我们的例子,{1..$END}
中x=1, y=$END
,前者是整数,后者是字符串,不符合以上任何一个条件,所以也就不展开了。
ABS文档里面也有例子介绍这个语法的使用方法,中间也提到这点。
<span>$ </span><span>for</span><span> i </span><span>in</span><span> </span><span>{</span><span>1.</span><span>.</span><span>$END</span><span>};</span><span> </span><span>do</span><span> echo $i</span><span>;</span><span> </span><span>done</span><span> </span><span>{</span><span>1.</span><span>.</span><span>5</span><span>}</span>
从上面的结果可以看出,只是显示了{1..5}
这个结果。如果要得到数值序列,有很多种方法。第一种是用seq start end
这种形式来替换{start..end}
,如:
<span>$ </span><b><font color="#008000"><span>for</span><span> i </span><span>in</span><span> </span><span>`seq 1 $END`</span><span>;</span><span> </span><span>do</span><span> echo $i</span><span>;</span><span> </span><span>done</span></font></b>
当然如果你一定要用{start..end}
,可以用eval
命令:
<span>$ </span><font color="#008000"><b><span>for</span><span> i </span><span>in</span><span> </span><span>`eval echo {1..$END}`</span><span>;</span><span> </span><span>do</span><span> echo $i</span><span>;</span><span> </span><span>done</span></b></font>
这里eval echo {1..$END}
后的结果为{1..5}
:
最直观的是用循环了:
<span>$ </span><b><font color="#008000"><span>for</span><span> </span><span>((</span><span>i</span><span>=</span><span>0</span><span>;</span><span>i</span><span><</span><span>$END</span><span>;</span><span>i</span><span>++))</span><span> </span><span>do</span><span> echo $i</span><span>;</span><span> </span><span>done</span></font></b>
当然,肯定有其它方法,只要你愿意去思考。
==================================================
这是 Bash One-Liners Explained 系列的第二篇文章。在这一篇里,我会给你们介绍如何用 Bash 来完成各种各样的字符串操作。我会选择用最合适的 Bash 方法,各种常见的语法和技巧,向各位阐明如何用 Bash 内置的命令和 Bash 编程语言来完成各式各样的任务。
$ echo {a..z}
这一行命令用到了括号展开(Brace expansion)功能,它可以用于生成任意的字符串。{x..y}
是一个序列表达式,其中 x 和 y 都是单个字符,这个表达式展开后包含 x 与 y 之间的所有字符。
运行上面的命令会生成从 a 到 z 的所有字母:
<font color="#008000"><b>$ echo {a..z} a b c d e f g h i j k l m n o p q r s t u v w x y z</b></font>
<b><font color="#008000">$ printf "%c" {a..z}</font></b>
这是一个 99.99% 的人都不知道的非常棒的技巧。如果你在printf
命令之后指定一个列表,最终它会循环依次打印每个元素,直到完成为止。
在这一行命令中,printf 的格式为"%c"
,代表一个字符(character),后面的参数是从 a 到 z 的字符列表,字符之间以空格分隔。所以,当printf
执行时,它依次输出每个字符直到所有字符全被处理完成为止。
下面是执行的结果:
abcdefghijklmnopqrstuvwxyz
输出的结果最后不包含换行符,因为printf
的输出格式是"%c"
,其中并没有包含\n
。如果你想输出完整的一行,可以简单地在字符列表后面增加一个$'\n'
:
$ printf "%c" {a..z} $'\n'
$'\n'
代表换行符,这是一个常用的技巧。
另外一种方式是,通过 echo 来输出 printf
的结果:
$ echo $(printf "%c" {a..z})
这一行命令用到了命令替换功能:执行printf "%c" {a..z}
命令然后用执行的输出替换命令。然后,echo
打印输出结果时会带上换行符。
如果你想要每一行仅输出一个字母,在字符后面增加一个换行符:
$ printf "%c\n" {a..z}
输出:
a b ... z
如果想要快速地将 printf
的结果保存到变量中,可以使用-v
选项:
<b><font color="#008000">$ printf -v alphabet "%c" {a..z} </font></b>
结果会将abcdefghijklmnopqrstuvwxyz
保存到变量alphabet
中。
类似地,你也可以利用同样的语法生成一个数字列表,例如从1到100:
$ echo {1..100}
输出:
1 2 3 ... 100
或者,如果你忘记这种方法,可以使用 seq 命令来做这个事情:
$ seq 1 100
$ printf "%02d " {0..9}
这里我们又用到了printf
的循环输出功能,这一次的输出格式为"%02d "
,意思是在输出数字的时候,如果不满两位就用0补齐。同时,输出的元素是 0 到 9的列表(括号展开后的结果)。
输出结果:
00 01 02 03 04 05 06 07 08 09
如果你使用的是最新的 Bash 4 版本,你可以使用加强的括号展开功能:
$ echo {00..09}
老版本不包含该特性。
$ echo {w,t,}h{e{n{,ce{,forth}},re{,in,fore,with{,al}}},ither,at}
这是一个滥用括号展开的例子,看看最终输出的结果是什么:
when whence whenceforth where wherein wherefore wherewith wherewithal whither what then thence thenceforth there therein therefore therewith therewithal thither that hen hence henceforth here herein herefore herewith herewithal hither hat
是不是很棒?
你可以通过括号展开生成一组单词或者符号的排列。例如:
$ echo {a,b,c}{1,2,3}
上面的命令会生成以下结果:a1 a2 a3 b1 b2 b3 c1 c2 c3
。首先,它取出第一个括号中的第一个元素a
,然后依次与第二个括号{1,2,3}
的所有元素组合,生成a1 a2 a3
,依此类推。
$ echo foo{,,,,,,,,,,}
这一行命令两次利用了括号展开功能,字符串foo
与10个空字符串组合,最终生成10分拷贝:
foo foo foo foo foo foo foo foo foo foo foo
$ echo "$x$y"
这一行命令简单地将两个变量的值连接在一起,所以如果x
变量的值为foo
,而y
的值为bar
,则结果为foobar
。
注意,这里的"$x$y"
是加了双引号的,如果忘记加了,echo
会将$x$y
当成常规的命令行参数去解析。所以,如果$x
在开头包含-
,它就变成一个命令行参数,而不是被 echo 输出的内容。
x=-n y=" foo" echo $x$y
执行后的输出:
foo
相反,正确书写的方式执行后的结果如下所示:
x=-n $ y=" foo" $echo "$x$y" -n foo
不过,如果你要将两个字符串相连的结果赋值给变量,是可以将双引号省略的:
var=$x$y
假设变量$str
的值为foo-bar-baz
,如果你想按-
分割成多个段并遍历它,可以使用read
命令,并且设置 IFS 的值为-
:
$ IFS=- read -r x y z <<< "$str"
这里我们使用read x
命令从标准输入读取内容,分割后并依次保存到x y z
变量中。其中,$x
为 foo
, $y
为 bar
, $z
为 baz
。
另外要留意的一处是here-string
操作符<<<
,可以很方便地将字符串传递给命令的标准输入。在这个例子中,$str
的内容传给 read
命令的标准输入。
你也可以将分割后的几个字段保存到数组类型的变量中:
$ IFS=- read -ra parts <<< "foo-bar-baz"
在这里,-a
选项告诉read
命令将分割后的元素保存到数组parts
中。随后,你可以通过${parts[0]}
, ${parts[1]}
和${parts[0]}
来访问数组的各个元素,或者通过${parts[@]}
来访问所有元素。
$ while IFS= read -rn1 c; do # do something with $c done <<< "$str"
这里我们通过指定-n1
参数,让read
命令依次读入一个字符,类似地,-n2
说明每次读入两个字符。
<b><font color="#008000">$ echo ${str/foo/bar}</font></b>
这一行命令用到了参数展开的另外一种形式:${var/find/replace}
,找到$var
变量中的find
字符串,并将它替换成bar
。
要想替换所有出现的字符串"foo"
,请使用${var//find/replace}
的形式:
$ echo ${str//foo/bar}
$ if [[ $file = *.zip ]]; then # do something fi
这一行命令是说,如果$file
的值匹配*.zip
,则执行if
语句里的命令。这种语法下的模式是最简单的通配符(glob pattern)匹配,通配符包括* ? [...]
。其中,*
可以匹配一个或者多个字符, ?
只能匹配单个字符,[...]
能够匹配任意出现在中括号里面的字符或者一类字符集。
下面是另外一个例子,用来判断回答是否匹配 Y 或者 y:er is Y or y:
$ if [[ $answer = [Yy]* ]]; then # do something fi
$ if [[ $str =~ [0-9]+\.[0-9]+ ]]; then # do something fi
这一行命令检查$str
是否能够匹配正则表达式[0-9]+\.[0-9]+
,即两个数字中间包含一个点号。正则表达式的规范可以通过 man 手册查询: man 3 regex
$ echo ${#str}
这里我们又用到了参数展开(也可以叫参数替换)的语法: ${#str}
,它返回$str
变量值的长度。
$ str="hello world" $ echo ${str:6}
这一行命令通过子串提取操作,从字符串hello world
中取到了子串world
。子串提取操作的语法格式为${var:offset:length}
,它的意思是说从变量var
中,提取第offset
个位置(下标从0开始计算)开始的总共length
个数的字符。在我们这个例子中,忽略了length
,默认会返回所有剩余的字符。
下面是另外一个例子,返回$str
变量中第7、8位置的两个字符:
$ echo ${str:7:2}
输出结果为or
。
$ declare -u var $ var="foo bar"
Bash 中的内置命令 declare
可以用于声明一个变量,或者设置变量的属性。在这个例子中,通过指定-u
选项,使得变量$var
在赋值时,就会自动地将内容转换成大写的格式。现在你 echo 它,可以看到所有内容已经变成大写了:
$ echo $var FOO BAR
注意,-u
选项也是在 Bash 4 新版本中引入的功能,在低版本下是没有的。类似地,你还可以使用 Bash 4 提供的另外一种参数展开语法${str^^}
,也可以将字符串转换成太写的格式:
$ str="zoo raw" $ echo ${str^^}
$ declare -l var $ var="FOO BAR"
同上面一条类似,-l
选项声明变量的小写属性,使得其值转换成小写的格式:
$ echo $var foo bar
同样,只有 Bash 4 以及以上的版本才支持-l
选项。另外一种方式是使用参数展开语法:
$ str="ZOO RAW" $ echo ${str,,}
我补充一句,如果是 Bash 4 以下,还是老老实实地用tr
命令就可以了。
from: http://kodango.com/generate-number-sequence-in-shell
from: http://kodango.com/bash-one-liners-explained-part-two#more-2797