Darcy Tang’s Blog

记录一点编程心得

使用Spark对ElasticSearch进行读取

ElasticSearch for Apache Hadoop是ES提供的工具库,让Hadoop、Pig、Hive等可以比较原生的方式去和ES交互。 目前提供了mapreduce、hive、pig、cascading、spark、storm的集成。

下面以Spark为例,演示如何利用这个工具库去读取ES记录

官方指南

安装

我使用的是spark-shell交互式环境进行的测试,所以需要手动下载elasticsearch-hadoop的jar包。 在maven项目中可以通过添加

1
2
3
4
5
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch-hadoop</artifactId>
    <version>2.1.0.Beta3</version>
</dependency>

这是包含了所有支持的jar包,也可以下载单独的spark支持包。

对于Spark,还需要下载Kryo,来替代Spark自带的序列化包。

最后elasticsearch-hadoop只支持Java 1.7以上版本,所以需要看看Java环境是否匹配。

启动spark-shell时,使用以下命令加载特定jar包

1
./bin/spark-shell -jars ./elasticsearch-hadoop-2.1.0.Beta3.jar;./kryo-3.0.0/jar

指定序列化工具

1
2
3
4
5
import com.esotericsoftware.kryo.KryoSerializable
import org.apache.spark.SparkConf

val conf = new SparkConf()
conf.set("spark.serializer", classOf[KryoSerializer].getName)

读取

1
2
3
4
5
6
7
8
9
10
11
12
import org.apache.hadoop.conf.Configuration
import org.elasticsearch.hadoop.mr.EsInputFormat
import org.apache.hadoop.io.Text
import org.apache.hadoop.io.MapWritable

val conf = new Configuration()
conf.set("es.resource", "highrisk/blacklist") //指定读取的索引名称
conf.set("es.nodes", "127.0.0.1")
conf.set("es.query", "?q=me*")  //使用query字符串对结果进行过滤
val esRDD = sc.newHadoopRDD(conf, classOf[EsInputFormat[Text, MapWritable]],
                                  classOf[Text], classOf[MapWritable]))
val docCount = esRDD.count();

读取出来的记录为key-value形式,key为Text类型,value为MapWritable类型。 接下来就可以利用Spark对esRDD进行各种map、flatmap、reduceByKey的操作了。

写入

参考用Spark处理数据导入ElasticSearch

1
2
3
4
5
6
7
8
9
10
import org.apache.spark.SparkConf
import org.elasticsearch.spark._

val conf = new SparkConf()
conf.set("es.index.auto.create", "true")
conf.set("es.nodes", "127.0.0.1")

val numbers = Map("one" -> 1, "two" -> 2, "three" -> 3)
val airports = Map("OTP" -> "Otopeni", "SFO" -> "San Fran")
sc.makeRDD(Seq(numbers, airports)).saveToEs("spark/docs")

目前elasticsearch-hadoop对Spark的支持还比较简单,想要对记录进行过滤就只有通过query字符串或者全部读取后在Spark中过滤,对规模比较大的索引或者复杂的过滤查询不友好。

配置

主要的配置 * es.resource * es.resource.read 默认与es.resource相同 * es.resource.write 默认与es.resource相同 * es.nodes 默认为localhost * es.port 默认为9200 * es.query 默认为none

完整的请查看Configuration

AngularJS+SpringMVC构建应用

利用AngularJS和SpringMVC搭建一个简单的应用,AngularJS负责前端,SpringMVC作为后端提供REST API。

SpringMVC的使用

SpringMVC中可以很方便的使用@RequestMapping注解提供REST API。

最基本的使用代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Controller
@RequestMapping(value = "/hello")
public class MyController {

  @RequestMapping(method=RequestMethod.GET)
  @ResponseBody
  public String getRoot(){

    return "Hello World";

  }

  @RequestMapping(value="/{path}", method=RequestMethod.GET)
  @ResponseBody
  public String getValue(@PathVariable String path){
    return "Path is " + path;
  }

}

使用@Controller注解指定当前类为SpringMVC的一个controller,使用@RequestMapping表明此controller会处理所有以/hello开头的URL。

@RequestMapping最常用的两个参数就是valuemethod,分别指定了拦截的URL和请求方法。

MyController类中的两个方法还可以分别再使用@RequestMapping(value)再继续指定处理以hello为前缀的URL。例如@RequestMapping(value="/{path}"),可处理类似/hello/path的URL,并结合@PathVariable将path作为URL参数获取。

这个小Demo只是将SpringMVC作为后台REST API的提供方,也就是说只使用了MVC中的Controller和Model,View就交给AngularJS,所以使用@ResponseBody直接将方法返回值作为响应,不经过View的渲染啥的。

简单的AngularJS应用

AngularJS是Google推出的前端MVC框架,也有完整的Controller、View、Model等概念,而且上手非常简单,熟悉几个html标签就能开始使用了。

建立AngularJS项目可以从GitHub上clone angular-seed

核心的代码都在app目录中,其典型结构如下:

  • css 存放css文件
  • img 存放图片
  • js 存放js代码,我们自己的代码基本都在这儿,一般按照功能和层次划分为这样几个文件:app.js, controller.js, services.js, filters.js, directives.js, animations.js。从名字就可以看出分别是在对这些概念进行定义和使用
  • lib 存放js库
  • partials 存放html模板,angularjs发送到客户端的就是这些html模板

基本概念

demo中涉及的几个概念:

  • app
  • controller
  • service
  • router
  • resource
  • view
  • scope

demo的基本功能就是在html模板中新建一个app和两个controller,然后自定义一个service使用REST API和SpringMVC后端交互操作hello这个resource,在controller中调用此service,html模板间的跳转通过router定义。

Alt text

借用AngularJS官方的一张图来说明下关系:

在template中声明app,app包含controller,controller调用service,service操作resource。

层次和调用关系还是很清晰的,和Spring也差不多。

template、app、service、controller的具体代码如下

template

1
2
3
4
5
6
<!doctype html>
<html lang="en" ng-app="myApp">
<body>
  <div ng-view></div>
</body>
</html>

app

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'use strict';

// 新建myApp,并声明会使用ngRoute、myApp.services、myApp.controllers三个module
// AngularJS会实现module的自动注入
angular.module('myApp', [
  'ngRoute',
  'myApp.services',
  'myApp.controllers'
]).
config(['$routeProvider', function($routeProvider) { // 配置app对应的URL路由,请求此URL时返回的view template和template对应的controller
  $routeProvider.when('/hello', {templateUrl: 'partials/list.html', controller: 'ListCtrl'});
  $routeProvider.when('/hello/:path', {templateUrl: 'partials/detail.html', controller: 'DetailCtrl'});
  $routeProvider.otherwise({redirectTo: '/hello'});
}]);

service

1
2
3
4
5
6
7
8
9
10
11
12
'use strict';

// 新建myService,并声明会使用ngResource这个module
var myService = angular.module('myApp.services', ['ngResource']).
  value('version', '0.1');
// 新建Hello这个资源,并指定资源的访问路径和访问方法
myService.factory('Hello', ['$resource',
  function($resource){
    return $resource('api/hello/:path', {}, {
      list: {method:'GET', isArray:true}
    });
  }]);

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'use strict';

// 新建ListCtrl和DetailCtrl两个controller
angular.module('myApp.controllers', []).
  controller('ListCtrl', ['$scope', 'Hello', // 声明要使用Hello
   function($scope, Hello) {
    $scope.hello = Hello.list();             // 调用Hello服务的list方法,赋值给模板中的hello元素
  }])
  .controller('DetailCtrl', ['$scope', '$routeParams', 'Hello',
   function($scope, $routeParams, Hello) {
    $scope.hello = Hello.get({path:$routeParams.path}, function(hello){
      $scope.detail = hello.detail;
    });
  }]);
1
2
3
4
5
6
7
8
<div class="container-fluid">
  <div class="row-fluid">
    <div class="span10">
      <!--Body content-->
          <p></p>
    </div>
  </div>
</div>

在Nagios中实时监控日志

最近需要利用Nagios对Linux和Windows上的某个日志文件进行实时的监控,虽然这个活由Nagios来做不太合适,但最后 还是记录一下找到的解决方案。

Windows

在Windows系统上使用NSClient++自带了对系统日志和日志文件的实时监控功能,只需要简单的配置下就可以使用了。当然还需要在服务器 上运行NSCA服务才能接收客户端主动推送的数据。

以下为开启对Windows系统日志监控的客户端配置,更详细配置可以参考Real time event-log monitoring with NSClient++和NSClient++的文档

[/modules]
CheckLogFile = enabled
CheckEventLog = enabled

[/settings/NSCA/client]
hostname=<nagios_hostname> # 需要和服务器端设定的nagios host一致

[/settings/NSCA/client/targets/default]
address=168.1.194.1  
password=nagios   # password和encryption配置需要和服务器端的nsca配置一致
encryption=1

[/settings/eventlog/real-time]
enabled = true

[/settings/eventlog/real-time/filters/run_log]  # run_log为nagios service名称
filter=type in ( 'warning', 'error') AND source = 'Puppet' 
target=NSCA
severity=OK
syntax=%type%: %strings%

Linux

配合incron

在nagios exchange里找到了check_logfiles这个插件,配合incron就可以实现实时的日志监控了。

incron是类似cron的工具,cron是基于时间,incron则是基于文件事件,底层使用了inotify系统调用。

以下是check_logfiles的配置文件,主要功能就在supersmartscript,意思就是在每找到一行新的日志信息时执行script指定的perl脚本。 类似的配置有supersmartpostscript,在一次检查完后执行perl脚本。 在脚本中也支持部分变量的调用,在$MACROS中定义即可在script中使用。 在这段配置里script部分调用系统命令,执行send_nsca发送监控信息到服务器。

$scriptpath = '/usr/bin/nagios/libexec:/usr/local/nagios/bin';
$MACROS = {
    CL_NAGIOS_HOST_ADDRESS => '%server_ip%',
    CL_NSCA_HOSTNAME => '%node_name%',
    CL_NSCA_PORT => 5667,
    CL_NSCA_CONFIG_FILE => '%send_nsca.cfg%'
};
@searches = (
  {
    options => 'supersmartscript,noprotocol',
    tag => 'puppet',
    logfile => '%puppet_run_log%',
    criticalpatterns => [
      'Tongtech',
      'err'
    ],
    script => sub {
      (my $line = "$ENV{CHECK_LOGFILES_NSCA_HOSTNAME}\t$ENV{CHECK_LOGFILES_SERVICEDESC}\t$ENV{CHECK_LOGFILES_SERVICESTATEID}\t$ENV{CHECK_LOGFILES_SERVICEOUTPUT}");
      #system("echo '$line'");
      system("echo '$line'|%send_nsca% -H $ENV{CHECK_LOGFILES_NAGIOS_HOST_ADDRESS} -c $ENV{CHECK_LOGFILES_NSCA_CONFIG_FILE} " );
    }
  },
);

按照常规的./configure && make && make install安装好incron,使用incrontab -e 命令编辑,基本的格式为

<path-to-puppet_log> MODIFY <path-to-check_logfiles> -f <配置文件路径> --rununique
# <path> <mask> <command>
# path - 监控的文件
# mask - 监控的文件事件,MODIFY表示文件有改变
# command - 监控到指定文件的指定事件后执行的命令

具体的使用可以参考此文章incron的使用

这样利用incron监控puppet的日志文件,当文件有改变时执行check_logfiles检查,就会发送日志信息到服务器。 注意–rununique选项,如果不加此选项,文件改变可能会触发多次检查,check_logfiles就会发送重复的信息。

只使用check_logfiles

check_logfiles支持以daemon方式运行,所以可以指定较短的检查周期,实现实时发送。

之前的check_logfiles配置不变,只是不使用incron执行命令,直接在shell中执行

<path-to-check_logfiles> -f <配置文件路径> --rununique --daemon 1

check_logfiles就会变成守护进程,每隔一秒检查一次日志文件,如果有新的日志写入,就发送到服务器。

Facter在Windows上的一个小问题

最近在Windows上使用Puppet,总是遇到一个诡异的问题:在某些机器上无法获得ipaddress这个fact,直接使用facter命令行也不行,而且显得完全没有规律。

具体情况是:

  • facter 在所有机器上可以获得ipaddress
  • facter –puppet 在部分机器上出现 invalid address 错误

ipaddress这个fact是在facter/lib/facter/ipaddress.rb添加进来的。关键代码是:

1
2
3
4
5
6
7
Facter.add(:ipaddress) do
  confine :kernel => %w{windows}
  setcode do
    require 'socket'
    IPSocket.getaddress(Socket.gethostname)
  end
end

发现是IPSocket.getaddress函数不起作用,于是又把问题定位在Puppet自带的Ruby环境中的ipaddr.rb中,发现以下代码在某些机器上无法获得IP。

1
2
3
4
5
6
7
8
alias getaddress_orig getaddress
def getaddress s   
  if valid? s
    s   
  else
    getaddress_orig s   
  end 
end

一开始没发现哪有问题,后来仔细看了下valid中的正则表达式,发现不允许hostname中出现下划线

之前一直没注意过这问题,所以测试主机的hostname都是随意命名的,所以才会出现部分主机可以通过验证,部分不行的情况。

不过也是现在才知道原来主机名规范有一条是不能带下划线。

Windows命名的时候完全没这个限制,但是在Ubuntu里使用hostname命令修改时,如果带下划线会直接提示错误。

很奇怪自己以前在Ubuntu上怎么没遇到过这个错误,难道就鬼使神差的从没使用过带下划线的主机名?

但是现在问题又来了,为什么facter都可以,facter –puppet就不行呢?

于是又研究了一遍facter的加载机制,在facter取值的部分折腾好久,最后发现和这部分没关系。是因为执行facter时没有加载facter/lib/facter/ipaddr.rb,ipaddr.rb中对IPSocket#getaddress的重写未生效,直接使用了Puppet自带Ruby中的socket.so中定义的IPSocket#getaddress,这个函数定义里面就没有对主机名的正则验证。

想来Puppet也是为了做多平台支持,所以加了一个MonkeyPatch,对主机名做了验证。

现在又绕回来了,–puppet 到底干了啥?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 def self.load_puppet
      require 'puppet'
      Puppet.parse_config

      # If you've set 'vardir' but not 'libdir' in your
      # puppet.conf, then the hook to add libdir to $:
      # won't get triggered.  This makes sure that it's setup
      # correctly.
      unless $LOAD_PATH.include?(Puppet[:libdir])
        $LOAD_PATH << Puppet[:libdir]
      end
    rescue LoadError => detail
      $stderr.puts "Could not load Puppet: #{detail}"
 end

根据代码来看,只是把Puppet的libdir加入了\$LOAD_PATH

这个libdir里面都是些自定义的facter、tyep和provider,似乎和ipaddr.rb完全没关系。

又卡在这儿想了半天,才发现思路错了。

ipaddr.rb虽然在lib目录下,但是不会自动加载,需要显式require

于是注释掉

1
require 'puppet'

使用facter –puppet也就和正常一样了。

Puppet3.2的迭代器

Puppet在最新的3.2版本中实现了lambda风格的迭代器语法,而且支持链式语法。当然要使用的话,需要在puppet.conf中启用parser=future

目前已经实现的有:

  • each/foreach
  • slice
  • select
  • collect
  • reject
  • reduce

含义和作用域与Ruby中的基本一致,基本用法也一致。

1
2
3
4
$a = [1,2,3]
each($a) |$value| { notice $value } # 迭代数组

[1,20,3].select |$value| {$value < 10 }.each |$value| { notice $value } # 链式语法

根据说明,也可以使用多种风格的语法

1
2
3
4
5
6
each($x)  |$value| {  }
$x.each   |$value| {  }
$x.each() |$value| {  }

slice($x, 2) |$value| {  }
$x.slice(2)  |$value| {  }

或者

1
2
3
4
5
6
7
8
# Alternative 0 (as shown): Parameters are outside the lambda block.
[1,2,3].each |$value| { notice $value }

# Alternative 1: Parameters are inside the lambda block.
[1,2,3].each { |$value| notice $value }

# Alternative 2: A fat arrow is placed after the parameters.
[1,2,3].each |$value| => { notice $value }

但是我自己实验,只有

1
[1,2,3].each { |$value| notice $value }

的方式成功了。

其实这些函数都是通过添加Puppet自定义函数的形式实现的,lambda表达式作为一个参数进行处理,调一个call就执行了。

比如迭代哈希的关键代码就是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  receiver = args[0]
  pblock = args[1]
  foreach_Hash(receiver, self, pblock)
  def foreach_Hash(o, scope, pblock)
      return nil unless pblock
      serving_size = pblock.parameter_count

      enumerator = o.each_pair

      if serving_size == 1
        (o.size).times do
          pblock.call(scope, enumerator.next)
        end
      else
        (o.size).times do
          pblock.call(scope, *enumerator.next)
        end
      end
      o
  end

其实还是使用了Ruby中的each_pair这个迭代器。在Puppet中写的lambda表达式还是循环调用,并把each_pair作为每一次的参数传进去用。

感觉Puppet在解释lambda表达式时应该还做了些工作,今天就不研究了。

在Puppet中使用Hiera

Puppet3.x开始Hiera成为了必需的组件,实现类似ENC的功能,正好现在试试效果。

使用Hiera可以用更格式化的组织节点的定义,而且支持动态的查找加载。

以前在puppetmaster运行过程中加入pp文件,puppet是不会发现并加载此文件的。

使用

Puppet3.x自带了Hiera,默认的hiera配置路径是/etc/puppet/hiera.yaml,所以只需要新建此文件,即启用了Hiera。

1
2
3
4
5
6
7
8
9
10
11
12
13
# hiera.yaml的例子
---
:backends: # 搜索yaml和json格式的文件
  - yaml
  - json
:yaml:  # hiera数据文件的目录
  :datadir: /etc/puppet/hieradata
:json:
  :datadir: /etc/puppet/hieradata
:hierarchy:  # 在指定目录搜索以下名称的文件
  - "%{::clientcert}"
  - "%{::custom_location}"
  - common

Hiera能实现动态加载的主要点就在:hierarchy的配置,此配置项指定Hiera需要读取的数据文件名称,%{::clientcert}表示使用以节点的certname来查找。

比如certname=node1的节点在运行puppet agent --test时,puppetmaster就会去读取node1.yamlnode1.json两个文件。具体可用的变量可以在此查看。

Hiera数据

具体的Hiera数据可以使用yaml、json、puppet三种格式,puppet格式的目前没有详细文档说明。

这里以yaml格式的为例说明,json格式也差不多,就是会多很多”“。

1
2
3
4
5
6
7
8
9
# node1.yaml
---
string: str
array:
  - 1
  - 2
  - 3
hash:
   key: value

你可以自己写一个module,其中的init.pp可以这样写:

1
2
3
4
5
6
7
# init.pp
class my_mod(
  $string = hiera("string"),
  $array = hiera_array("array"),
  $hash = hiera_hash("hash")
) {
}

然后在site.pp中给node1加上这个class,node1在获取catalog时就会自动的从node1.yaml中加载数据并赋值给$string, $array, $hash三个变量。hiera(), hiera_array(), hiera_hash()是Hiera专门用来搜索数据的函数。

Hiera也支持自动赋值,如果你的node1.yaml文件是这样的:

1
2
3
4
5
6
7
8
9
# node1.yaml
---
my_mod::string: str
my_mod::array:
  - 1
  - 2
  - 3
my_mod::hash:
   key: value

那在my_mod的参数中就不需要再使用函数查找,Hiera会自动关联赋值。

在Hiera中指定Classes

如果想完全抛弃site.pp怎么办?用Hiera也可以办到。

只需要在site.pp中加上一句hiera_include("classes")

在node1.yaml中再添加上classes。

1
2
3
4
5
6
7
8
9
10
11
# node1.yaml
---
classes:
  - my_mod
my_mod::string: str
my_mod::array:
  - 1
  - 2
  - 3
my_mod::hash:
   key: value

那么以后节点的classes定义就可以完全通过

使用Git Submodule

最近clone的项目里面发现使用了Git的Submodule功能,所以在这儿记录一下基本的使用方法

添加submodule

1
git submodule add git@github.com:xxx/submoule.git src/submodule

在项目中就会自动生成一个.gitmodules文件来保存submodule的关系,再提交到远程库就ok了

clone项目

在clone带有submodule的项目,直接使用--recursive就可以递归地将所有submodule,包括submodule中的submodule都一次性clone到本地

1
git clone --recursive git@github.com:xxx/xxx.git

不然就得使用如下的命令组合来完成初次clone,这样还不能clone到submodule中的submodule

1
2
3
git clone git@github.com:xxx/xxx.git
git submodule init    # 将.gitmodules中的内容注册到.git/config中
git submodule update  # clone submodule并且checkout指定commit

更新项目

如果你不会到submodule中进行开发,那么每次submodule有了更新只需要再update一次就行了

1
2
git pull origin master
git submodule update --recursive # 必须在项目顶层目录执行此命令,也可以添加--init保证init执行

更新submodule

submodule其实也就是一个单独的Git repo,只是会在项目顶层目录的.gitmodules目录中记录下路径和submodule源的映射关系

# .gitmodules的内容
[submodule "src/cloud_controller_ng"]
    path = src/cloud_controller_ng
    url = https://github.com/cloudfoundry/cloud_controller_ng.git

但是这个文件中并没有指定submodule的commit id,git怎么知道该用submodule的哪个commit呢?

在顶层项目的.git/modules里会保存submodule的git配置,同时也就知道了使用的是submodule的哪个commit 而submodule的.git则只是记录了一下路径

# submodule目录中的.git的内容
gitdir: ../.git/modules/dea

所以在submodule中进行commit或者push就和普通repo完全一样,只是最后需要在外层repo中更新一下

现在假设已经在submodule目录或者单独的submodule项目目录中完成了addcommitpush操作,或者执行了git pull获取了新的源码 这时需要回到外层项目,再次更新一遍

1
2
3
4
git status  # 会提示submodule有new commit
git add .   # 添加更改
git commit -m "update submoule"
git push

在另一个项目repo中执行以下命令,就可以将submodule的commit同步了

1
2
git pull
git submodule update --recursive  # 可以加上--init

其他

1
2
3
git submoule foreach 'echo $path `git rev-parse HEAD`'  # foreach 可以对每个submodule执行shell命令

git help submodule   # git帮助文档

使用awk比较文件

平时在CLI环境里面需要经常比较两个小文件的内容,最近专门搜索了下,收集了两个简单的比较方案。

方案1:

1
cat a | grep -vFf b

如果想要不区分大小写,加-i参数即可

方案2:

如果数据量到达1000w以上,grep很容易占满内存,无法使用

这个时候就需要使用awk了

比如要比较a和b两个文件; a b 1 1 qw 2 qq d 123 cd

列出b文件中完全不包含a文件的行

1
2
3
awk 'ARGIND==1 {arr[$0]} ARGIND>1&&!($0 in arr) {print $0}' a b

awk 'NR==FNR {arr[$0]} NR>FNR&&!($0 in arr) {print $0}' a b

解释:

首先awk会按顺序先处理a文件,在处理b文件;

然后根据awk的环境变量列表:

$n           当前记录的第n个字段,字段间由FS分隔。
$0           完整的输入记录。
ARGC         命令行参数的数目。
ARGIND       命令行中当前文件的位置(从0开始算)。
ARGV         包含命令行参数的数组。
CONVFMT      数字转换格式(默认值为%.6g)
ENVIRON      环境变量关联数组。
ERRNO        最后一个系统错误的描述。
FIELDWIDTHS  字段宽度列表(用空格键分隔)。
FILENAME     当前文件名。FNR同NR,但相对于当前文件。
FS           字段分隔符(默认是任何空格)。
IGNORECASE   如果为真,则进行忽略大小写的匹配。
NF           当前记录中的字段数。
NR           当前记录数。
OFMT         数字的输出格式(默认值是%.6g)。
OFS          输出字段分隔符(默认值是一个空格)。
ORS          输出记录分隔符(默认值是一个换行符)。
RLENGTH      由match函数所匹配的字符串的长度。
RS           记录分隔符(默认是一个换行符)。 
RSTART       由match函数所匹配的字符串的第一个位置。
SUBSEP       数组下标分隔符(默认值是\034)。

NR==FNR时是在处理a,将当前行放入数组

NR>FNR时是在处理b,如果当前行不在数组中,则打印

相应的ARGIND==1 正在处理a,ARGIND > 1 正在处理文件b

Ruby2.0关键字参数使用小记

Ruby2.0发布了,于是下来试试手。 下载了源码包,直接./configure && make && make install安装,没加特殊的flag。

安装过程中出现了几个问题:

  • 系统时间不对,在make的时候检查文件时间戳出错,不断的重复执行configure,使用ntp服务同步时间解决
  • 在pry环境中不能使用上下方向键,错误信息是rb-readline有问题,在pry的 Github Issues 里面找到问题,是rb-readline版本问题,重新安装最新版本解决

然后试用了几个2.0的新特性,对我来说关键字参数最适用,其他几个RefinementsLazy enumerablesModule prepending平时元编程和函数编程用得不多,暂时用不上。

对于关键字参数,Python里面已经有了,没什么好说的。

Ruby中关键字参数的两个要点:

  • 位置:定义的时候只能是 a, *b, c: 1, **d, &e 的顺序
  • 使用:调用时必须指明参数名称。这点和Python不同,Python可以根据参数的顺序来推断。

对于如下定义的函数

1
2
3
def func(a: 1, b: 2, c: 3)
  print a,b,c
end

调用时使用func(4,5,6)会提示wrong number of arguments, 只能是func(a: 4, b: 5, c: 6)的形式,这个时候顺序可以随意了。