HackToday Walk Blog


  • Home

  • Tags

  • Archives

  • Search

qpid 接收消息编程

Posted on 2013-10-18

qpid 是符合AMQP规范的apache 许可证的消息中间件,目前在openstack中作为一种可选的消息中间件服务配置,其他还有rabbitmq和zeroMQ

如果看过qpid的编程API文档的话,会看到比较简单的一个例子, 如下(接收消息打印消息内容)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#following code is from
# http://qpid.apache.org/releases/qpid-0.24/messaging-api/python/examples/drain.html


import optparse
from qpid.messaging import *
from qpid.util import URL
from qpid.log import enable, DEBUG, WARN

parser = optparse.OptionParser(usage="usage: %prog [options] ADDRESS ...",
description="Drain messages from the supplied address.")
parser.add_option("-b", "--broker", default="localhost",
help="connect to specified BROKER (default %default)")
parser.add_option("-c", "--count", type="int",
help="number of messages to drain")
parser.add_option("-f", "--forever", action="store_true",
help="ignore timeout and wait forever")
parser.add_option("-r", "--reconnect", action="store_true",
help="enable auto reconnect")
parser.add_option("-i", "--reconnect-interval", type="float", default=3,
help="interval between reconnect attempts")
parser.add_option("-l", "--reconnect-limit", type="int",
help="maximum number of reconnect attempts")
parser.add_option("-t", "--timeout", type="float", default=0,
help="timeout in seconds to wait before exiting (default %default)")
parser.add_option("-p", "--print", dest="format", default="%(M)s",
help="format string for printing messages (default %default)")
parser.add_option("-v", dest="verbose", action="store_true",
help="enable logging")

opts, args = parser.parse_args()

if opts.verbose:
enable("qpid", DEBUG)
else:
enable("qpid", WARN)

if args:
addr = args.pop(0)
else:
parser.error("address is required")
if opts.forever:
timeout = None
else:
timeout = opts.timeout

class Formatter:

def __init__(self, message):
self.message = message
self.environ = {"M": self.message,
"P": self.message.properties,
"C": self.message.content}

def __getitem__(self, st):
return eval(st, self.environ)

conn = Connection(opts.broker,
reconnect=opts.reconnect,
reconnect_interval=opts.reconnect_interval,
reconnect_limit=opts.reconnect_limit)
try:
conn.open()
ssn = conn.session()
rcv = ssn.receiver(addr)

count = 0
while not opts.count or count < opts.count:
try:
msg = rcv.fetch(timeout=timeout)
print opts.format % Formatter(msg)
count += 1
ssn.acknowledge()
except Empty:
break
except ReceiverError, e:
print e
except KeyboardInterrupt:
pass

conn.close()

connection和session是1对多的关系,每个session保证消息的顺序接收,让session创建对应的sender和receiver,这里我们只需要创建receiver

这个程序看起来很好,如果直接传一个地址, 比如(我们想要接收openstack glance的message)
运行如下:
python drain.py -b admin/qpid@localhost glance

如果我们想要实现连接断掉后重新自动连接,我们可以传入参数 -r
上面的程序有个问题,如果没有收到消息就断开连接了。

新需求1: 我们要实现持续的监听接收消息, 改一下

1
2
3
4
5
6
7
try:
msg = rcv.fetch(timeout=timeout)
print opts.format % Formatter(msg)
count += 1
ssn.acknowledge()
except Empty:
time.sleep(0.5)

这样就可以了。

还不行,

新需求2:我们要实现断后重新自动连接, 可以, 传入参数 -r,

解决了

问题出现了,你会发现在service qpidd restart后,程序会抛出exception
qpid.messaging.exceptions.NotFound: no such queue: glance

新需求3:显然这是由于我们创建的queue不是durable的,所以需要在qpid restart后能够正常运行,而不是退出。
这就需要我们创建的queue能够在接收到消息的时候自动创建完成,

解决方法: rcv = ssn.receiver(addr + “; {create: always}”)

这样就完成queue的按需创建

新需求4: qpid 默认的reconnect上面的是基于固定interval,我们想改变重新建立连接的算法,实现2的指数式建立连接
解决方法: 我们写入自己的自动重连方法, 一个简单的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def reconnect():
global rcv
global ssn
attempt = 0
delay = 1
while True:
if conn.opened():
try:
conn.close()
except exceptions.ConnectionError:
pass
attempt += 1
print "The %s time attempt for reconnecting qpid server" % str(attempt)
try:
connection_init()
conn.open()
except exceptions.ConnectionError, e:
delay = min(2 * delay, 60)
time.sleep(delay)
pass
else:
break
print "qpid server reconnection created"
ssn = conn.session()
rcv = ssn.receiver(addr + "; {create: always}")

这样就OK了

总结: 经过上面的需求变化,我们新的接收消息程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import optparse
import time
from qpid.messaging import *
from qpid.util import URL
from qpid.log import enable, DEBUG, WARN

parser = optparse.OptionParser(usage="usage: %prog [options] ADDRESS ...",
description="Drain messages from the supplied address.")
parser.add_option("-b", "--broker", default="localhost",
help="connect to specified BROKER (default %default)")
parser.add_option("-c", "--count", type="int",
help="number of messages to drain")
parser.add_option("-f", "--forever", action="store_true",
help="ignore timeout and wait forever")
parser.add_option("-r", "--reconnect", action="store_true",
help="enable auto reconnect")
parser.add_option("-i", "--reconnect-interval", type="float", default=3,
help="interval between reconnect attempts")
parser.add_option("-l", "--reconnect-limit", type="int",
help="maximum number of reconnect attempts")
parser.add_option("-t", "--timeout", type="float", default=0,
help="timeout in seconds to wait before exiting (default %default)")
parser.add_option("-p", "--print", dest="format", default="%(M)s",
help="format string for printing messages (default %default)")
parser.add_option("-v", dest="verbose", action="store_true",
help="enable logging")

opts, args = parser.parse_args()

if opts.verbose:
enable("qpid", DEBUG)
else:
enable("qpid", WARN)

if args:
addr = args.pop(0)
else:
parser.error("address is required")
if opts.forever:
timeout = None
else:
timeout = opts.timeout

count = 0
rcv = None
conn = None
ssn = None


class Formatter:

def __init__(self, message):
self.message = message
self.environ = {"M": self.message,
"P": self.message.properties,
"C": self.message.content}

def __getitem__(self, st):
return eval(st, self.environ)


def reconnect():
global rcv
global ssn
attempt = 0
delay = 1
while True:
if conn.opened():
try:
conn.close()
except exceptions.ConnectionError:
pass
attempt += 1
print "The %s time attempt for reconnecting qpid server" % str(attempt)
try:
connection_init()
conn.open()
except exceptions.ConnectionError, e:
delay = min(2 * delay, 60)
time.sleep(delay)
pass
else:
break
print "qpid server reconnection created"
ssn = conn.session()
rcv = ssn.receiver(addr + "; {create: always}")


def fetch():
global count
while not opts.count or count < opts.count:
try:
msg = rcv.fetch(timeout=timeout)
print opts.format % Formatter(msg)
count += 1
ssn.acknowledge()
except Empty:
time.sleep(0.5)
except exceptions.ConnectionError, e:
reconnect()
except Exception, e:
print e
raise e


def connection_init():
global conn
conn = Connection(opts.broker,
reconnect=opts.reconnect,
reconnect_interval=opts.reconnect_interval,
reconnect_limit=opts.reconnect_limit)


try:
connection_init()
conn.open()
ssn = conn.session()
rcv = ssn.receiver(addr + "; {create: always}")
fetch()

except ReceiverError, e:
print e
except KeyboardInterrupt:
pass
except exceptions.ConnectionError, e:
reconnect()


conn.close()

DevOps-chef的hello-world cookbook

Posted on 2013-09-20

在上一篇的文章中,我们简单了介绍chef的环境搭建,那么现在你肯定就跃跃欲试如何创建一个cookbook,真正体会自动化的配置管理的便捷之处。
废话少说,我们选取简单的hello-world为例子,

需求:

1.在workstation可以让多个节点自动化创建hello-world.txt文件
2.文件的owner是test, group是test

假设:

test用户和组是在节点上已经存在

实施:
(下面的步骤如果没有特殊说明,都是在workstation上运行的)

  1. 创建cookbook
1
knife  cookbook create hello_world
  1. 添加内容到recipe
    recipe就是主要的控制内容,让节点完成具体的操作。
    

cat recipes/default.rb 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#
# Cookbook Name:: hello_world
# Recipe:: default
#
# Copyright 2013, YOUR_COMPANY_NAME
#
# All rights reserved - Do Not Redistribute
#
# recipes/default.rb
template "#{ENV['HOME']}/hello-world.txt" do
source 'hello-world.txt.erb'
mode '0644'
owner 'test'
group 'test'
end

上面的指令是:在用户对应的目录(Linux/Unix:~ Windows: %HOMEPATH%)下创建一个文件hello-world.txt:
内容是放在hello-world.txt.erb模板里面。

  1. 添加对应的模板
1
2
3
4
5
6
7
8
9
 cat templates/default/hello-world.txt.erb 如下:

<% # templates/default/hello-world.txt.erb %>
Hello World!


Chef Version: <%= node[:chef_packages][:chef][:version] %>
Platform: <%= node[:platform] %>
Version: <%= node[:platform_version] %>
  1. 上传cookbook到chef server
1
knife  cookbook  upload 'hello_world'
  1. 将相应的cookbook添加到对应节点run_list
1
knife node run_list add  'hello_world'

其中的node name,可以通过knife node list查看

  1. 检查相应的run list是否被node包含
1
2
3
4
5
6
7
8
9
  $ knife node show 
Node Name:
Environment: _default
FQDN:
IP:
Run List: recipe[hello_world]
Roles:
Recipes: hello_world
Platform: ubuntu 12.04
  1. 节点应用cookbook
1
knife ssh name:  -x  -P  "sudo chef-client"
  1. 验证node是否正确配置, 登录到node节点,检查
1
2
3
4
5
6
7
8
 cat ~/hello-world.txt 如下:

Hello World!


Chef Version: 11.6.0
Platform: ubuntu
Version: 12.04

可见按照上面的步骤我们就顺利的完成了一个很简单的cookbook开发,熟悉了流程后,后面可以继续研究复杂cookbook的编写。

其他参考:

  1. http://technology.customink.com/blog/2012/05/28/provision-your-laptop-with-chef-part-1/
  2. https://wiki.opscode.com/plugins/viewsource/viewpagesrc.action?pageId=18645173

DevOps-chef的多节点环境搭建

Posted on 2013-09-20

前言:
前段时间一直想试验一下DevOps的一些配置管理工具,后来因为某些原因,就重点研究了chef。以自己的机器搭建了一个典型的多节点实验环境。

架构:
根据官方的chef的架构介绍,主要包括三大部分,

  1. chef-server
  2. chef workstation
  3. chef-node

source:http://docs.opscode.com/chef_overview.html

上面的这个图很清晰的表达了各部分的联系,所以推测我们需要安装的主要是server和workstation部分,而node应该是通过客户端来让chef自动化安装的

实施:

下面我们介绍如何实施一个代表性的环境

环境的前提配置:

  • 安装的OS环境: Ubuntu 12.04
  • 虚拟化软件: VirtualBox
  • 使用的网络类型: Host-Only NAT
  • 假设用户都自己配置好了, 主机对应的FQDN
  1. server 的安装

http://docs.opscode.com/install_server.html 过程比较简单, 就是安装包,然后运行配置命令。
具体如下:chef_11.6.0-1.ubuntu.12.04_amd64.deb

安装:

1
sudo dpkg -i chef-server_11.0.8-1.ubuntu.12.04_amd64.deb

配置:

1
sudo chef-server-ctl reconfigure

验证安装:

1
sudo chef-server-ctl test

其实这个似乎不能全部pass,感觉chef的集成测试集可能有些问题

  1. workstation 的安装 (http://docs.opscode.com/chef/install_workstation.html)
    过程稍微比server安装复杂,但还算简单明了, 主要是安装client ,配置client到server访问。具体如下:

    2.1 安装client:

    1
    sudo dpkg -i  chef_11.6.0-1.ubuntu.12.04_amd64.deb

    2.2 安装配置chef-repo:

    1
    git clone git://github.com/opscode/chef-repo.git

    因为chef-repo是存放cookbooks的地方,knife命令行工具会从chef-repo上传数据到chef-server,
    这样chef-client就从server可以应用相应的cookbooks了,所以我们可以知道,chef-repo需要配置和server的访问,
    主要包括,.pem files and knife.rb files

    2.3 创建.chef目录

    在 chef-repo目录下,创建.chef目录,并且修改.gitignore文件,添加 .chef

    2.4 配置:

    1
    knife configure --initial

    注意: 输入相关的信息,主要是server的url,client的key,client注册server所需要的validator和
    validator private key(默认的是chef-validator和server端 /etc/chef/validation.pem 文件)
    还有admin的private key,所以我们需要从server端copy两个文件到workstation机器上,
    (才能在运行knife configure输入恰当的private key 信息)

    admin 和 validator的private key 文件,即: admin.pem validation.pem,

    1
    2
    scp  root@:/etc/chef/admin.pem  ./
    scp root@:/etc/chef/validation.pem ./

    2.5 将knife.rb和pem文件移到 chef-repo的.chef目录下

    1
    cp **    /.chef

    2.6 验证 client 是否工作

    1
    2
    3
    knife client list

    knife user list

    就可以输出相关的server端的信息了

  1. Node 的自动化安装 (http://docs.opscode.com/install_bootstrap.html)

    下面的是在workstation上运行的,比较简单:

    3.1 bootstrap

    1
    knife bootstrap -x -P --sudo

    3.2 验证 node

    1
    knife client list

    正确的话,就会输出你的node节点的名字FQDN。
    注意: 因为workstation 在bootstrap的时候是需要ssh到node的,而且node也是需要到server访问的
    (依靠FQDN,就是你配置的server url),那么就意味着, node需要安装ssh server; node是可以解析server的FQDN的,
    可以在/etc/hosts添加相应的信息

总结:

经过1,2,3步骤,我们就搭建一个典型的chef 环境,包括三个节点,server, workstation和node
后面我们会给出一篇文章来说明如何创建一个cookbook,并且让node应用这个cookbook。

其他参考资料:

  1. http://www.opscode.com/blog/2013/03/11/chef-11-server-up-and-running/
  2. http://dev.classmethod.jp/server-side/chef-server-install/
  3. http://docs.opscode.com/install.html
  4. http://docs.opscode.com/chef_overview.html
  5. http://jtimberman.housepub.org/blog/2013/02/10/install-chef-11-server-on-centos-6/

shell的command和function分工

Posted on 2013-09-08

如果你经常编写shell脚本,有时会碰到类似的问题:
shell中定义了一个函数,这个函数和系统内建的命令同名, 例如

1
2
3
4
function cp() {
echo "This is a test"
cp
}

显然上面的是一个递归函数,这个递归没有退出条件,最后必然是导致Segmentation fault

如果我们不希望cp()里的继续调用自己,而是系统拷贝的命令,怎么办?
答案就是使用command命令,代码如下

1
2
3
4
function cp() {
echo "This is a test"
command cp ...
}

这样就保证了系统命令覆盖函数的优先使用权。

关于监控远端主机开放端口的3种方法

Posted on 2013-09-03
  1. telnet
1
telnet
  1. nc
1
nc -z -w5 ; echo $?
  1. nmap
1
sudo nmap -sS -v -p

优缺点:

  1. nc nmap便于脚本化
  2. nmap需要单独安装,nc一般ubuntu下自己安装了
  3. telnet 在windows下用的比较多

Devstack安装碰到python package的版本问题

Posted on 2013-09-03

因为一直在用devstack在一台虚拟机器上进行安装,openstack的havana的开发过程相关的依赖版本会不时的更新,
最近安装devstack发现总会出现一些service启动不了直接查看log一般是 version 不匹配,或者缺少相关的module,
显然是相关的package安装不匹配的缘故,如何解决

1
sudo pip install --upgrade  -r /opt/stack/nova/requirements.txt

如果安装还是失败,报version不匹配,那就在 /usr/local/lib/python2.7/dist-packages/ 删除相应的package,
(如果你的pip uninstall可以work的话, 那就最好用pip uninstall)再运行上面的命令。

qpid service 的root引发的权限问题

Posted on 2013-08-29

最近使用的一台虚拟机是root登录操作的,使用yum安装完qpid后,设置可以访问的用户名和密码时候,犯了一个低级错误,
直接使用下面的命令

1
saslpasswd2 -f /var/lib/qpidd/qpidd.sasldb  -u QPID qpid

使用qpid-tools查看queues的时候,发现老是出现认证无法通过,但是直接qpidd运行没有问题,
查看了一下log发现,/var/log/messages

1
unable to open Berkeley db /var/lib/qpidd/qpidd.sasldb: Permission denied

检查了一下/var/lib/qpidd/qpidd.sasldb的权限,发现用户为root(rw),难怪如此,
因为service 启动是以qpidd用户来运行的,没有w的权限。

解决方法:

设置 /var/lib/qpidd 用户和用户组为qpidd:qpidd

其实原来自己写过一篇qpid的setup的文章,都记录要配置这个,只是实际用起来偶犯健忘了。

eclipse36,eclipse42,java的融合问题

Posted on 2013-08-24

昨天被Junit搞的精疲力尽,使用的eclipse3.6一路出现莫名其妙的jvm运行错误,

最初怀疑java配置的问题,因为所有的代码都是在java7运行的,虽然在eclipse设置installed jre是java7,
但是系统(windows)的系统java_home没有更改,还是原来的java6。所以更改系统的java_home,发现仍然存在问题,
于是再仔细看了一下stack trace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
at java.lang.J9VMInternals.initialize(J9VMInternals.java:176)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:68)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:528)
at org.junit.runners.BlockJUnit4ClassRunner.createTest(BlockJUnit4ClassRunner.java:187)
at org.junit.runners.BlockJUnit4ClassRunner$1.runReflectiveCall(BlockJUnit4ClassRunner.java:236)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:233)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.pde.internal.junit.runtime.RemotePluginTestRunner.main(RemotePluginTestRunner.java:62)
at org.eclipse.pde.internal.junit.runtime.CoreTestApplication.run(CoreTestApplication.java:23)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:76)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:602)
at org.eclipse.equinox.internal.app.EclipseAppContainer.callMethodWithException(EclipseAppContainer.java:587)
at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:198)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:353)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:180)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:76)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:602)
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:629)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:584)
at org.eclipse.equinox.launcher.Main.run(Main.java:1438)
at org.eclipse.equinox.launcher.Main.main(Main.java:1414)

发现和equinox相关,检查target的Equinox是对应eclipse42的,会不会eclipse42的equinox在eclipse36跑会有问题,
于是,切换eclipse环境,用了eclipse42后,发现确实没有问题了。

虽然不敢肯定根本原因是不是这个,但是确实eclipse会提示运行不匹配的版本会有潜在问题。
之所有一直用eclipse36,是因为原来没发现类似的问题,好吧,那就迁到eclipse42吧。

pep8的问题(ubuntu12.04和Redhat 6.4)

Posted on 2013-08-17

前几天调试一个build失败的问题,发现服务器上的pep8检查总是无法通过,但是开发环境确没什么问题。经过调查发现,
Openstack的pep8已经采用了1.4.5的版本,开发环境用的原来的1.1,新的pep8采用了更严格的格式检查,
所以导致开发环境无法检查出这些问题。

ubuntu 12.04和Redhat 6.4对应的pep8源版本过旧,所以如果你用的默认的pep8,那么也会有这个问题。

而且eclipse的pydev插件用的也是旧的pep8检查。

解决方法:

升级对应的pep8,

1
pip install --upgrade pep8==1.4.5

如果你用的是python的虚拟环境,要检查对应的pep8版本是否一致,不一致的话,那就是requires文件里的版本配置的不对

Linux shell你所不知道的$($*和$@)

Posted on 2013-07-30

在$的相关的特殊符号中,有以下的几种需要注意的区别

1) 和 @
在shell中虽然都是展开位置参数可以用
$@和$
,但是两种有很大的差别,特别是在双引号的扩展下,比如

(a)

1
2
3
for i  in $*; do
echo "--$i"
done

(b)

1
2
3
for i in $@; do
echo "**$i"
done

如果传入的参数是”hi ni”

对于(a),

1
2
-- hi
-- ni

对于(b),

1
2
** hi
** ni

但是如果你说我传入的“hi ni”是一个参数($1),我不希望它输出为多个参数
那么我们更改脚本,改为

1
2
3
for i in "$*"; do
echo "--$1"
done
1
2
3
for i in "$@"; do
echo "**$i"
done

如果传入的参数是”hi ni”

1
2
-- hi ni
** hi ni

现在是相同的,没问题,但是如果传入的参数是空,那么你会发现下面的输出结果

1
--

这是因为(man bash)

1
2
"$*" is equivalent to "$1c$2c...", where c is the first character of the value of the IFS variable.
"$@" is equivalent to "$1" "$2" ...

显然是”$*”的展开是包含了一个空格,关于IFS的介绍,可以参看参考[1]

同样,我们可以知道如果设置IFS的话,那么多个参数的情况下,”$*”和”$@”输出是不一致的,比如(如果设置IFS=”,”)
输入参数, "hi ni" "you"

1
2
3
-- hi ni,you
** hi ni
** you

参考:
http://en.wikipedia.org/wiki/Internal_field_separator

1…131415…26

Kai Qiang Wu

This is a place for thinking and writing

253 posts
32 tags
GitHub
© 2020 Kai Qiang Wu
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4
Visitor Total Visit