momo zone

调核人的blog

Monthly Archives: 二月 2013

svndav gitolite and gitweb

svn服务器端建立在DAV基础上,重点自然是apache,由于之前没有摸过apache所以走了不少弯路。git服务器端建立在gitolite上,由于每个版本的安装方法还不太一样,所以也做了很多无用功。

PART1 -SVN:

每个发行版apache组件的包名称还都不一样,有的叫apache2,有的叫httpd,导致/etc下的配置档名称也不一样。这部分介绍以centos发行版为例。

step1. 建一个虚拟主机,便于发布一些内容。

编辑/etc/httpd/conf/httpd.conf 在末尾增加:

Include “vhosts.d/*.conf”

然后新建/etc/httpd/vhosts.d/repo-svn.conf,内容如下:

#默认虚拟主机端口9900
Listen 9900 
NameVirtualHost *:9900

#使用SSL
SSLEngine On
#使用SSL握手证书
SSLCertificateFile /data/www/apache.pem
#用于gitweb,下文有介绍

    DocumentRoot /data/www
    ServerName repo.yamutech.com
Alias /doc "/data/www/"
<Directory "/data/www">
    Options Indexes MultiViews FollowSymLinks
    AllowOverride None
    Order allow,deny
    Allow from all

#增加一个repo虚拟路径,用来列出版本库实际的文件目录
Alias /repo "/data/repo/"
<Directory "/data/repo/">
    Options Indexes MultiViews FollowSymLinks
    AllowOverride None
    Order allow,deny
    Allow from 192.168.8.63

step2. 编辑vi /etc/httpd/conf.d/subversion.conf

  DAV svn
#指定版本库目录,SVNParentPath和SVNPath的区别在于前者将指定目录下的所有子目录一并包含
  SVNParentPath /data/repo/svnroot
# SVNPath /data/repo/svnroot/test
  AuthType Digest
#注意这个需要和AuthUserFile里的字段一致
  AuthName "site general"
#  AuthDigestDomain /svn/svnroot
#用户名和密码文件
  AuthUserFile /data/repo/svnroot/passwd
#用户名权限配置文件
  AuthzSVNAccessFile /data/repo/svnroot/authz
#  
#所有操作都需要用户验证
        Require valid-user
#

passwd文件格式类似如下内容:
heihj:site general:18e8969c2b335d5334813a2ad4bdf2aa
jialq:site general:a29a999439b9e3de47fc3976699e8afb
gupf:site general:70d9e6d0ae019c4b36021101d9af86a4
ygm:site general:f67eeb9e435f2ae2ef24b79ee503afd6
yanggm:site general:9977d4938d34e370e4c08e3c96f13f8d
phq:site general:22b5abea72d25b6fbb24f5c94470ec71
pp:site general:fb5b9926c81d82f4c7cc11ca699f044c
gp:site general:f659ab4f518ce2b03b09b8967eac2103
ljh:site general:9724fa98cbc0dd18eb99a3fd2afe5d16
wc:site general:264ede607d4986652a380e0a9e11f90d

生成方法是使用htdigest pass_file username

authz文件格式类似如下内容:

#定义用户组
[groups]
ADMIN = AAA

#“/”指的是SVNParentPath或SVNPath目录,“:”前面指的是子目录。可见,如果不用SVNParentPath,设置权限的根目录将是每个版本库自身
[products:/]
@ADMIN = rw

[products:/dns_2]
@ADMIN = r
AA = rw
.......

step3. 创建一个svn版本库
cd /data/repo/svnroot
svnadmin create test

PART2 -GIT:

gitolite稍微麻烦点,我用的是分支版本,其他的版本可能不适用于此安装方法。

step1. 在服务器端新增git帐号:

adduser –system –home /home/git –shell /bin/bash -g git git

mkdir /home/git

chown git:git /home/git

step2. 在客户端生成管理员公钥私钥对:

ssh-keygen -t das

需要的话可以秘钥加密码

step3.  在服务器端安装gitolite

这里是针对的 git://github.com/ossxp-com/gitolite.git 这个3.0分支版本,其他版本并不适用。

su git

cd gitolite/src

./gl-system-install /usr/bin /usr/share/gitolite/conf /usr/share/gitolite/hooks

这个安装其实主要是把文件copy到对应的目录中去,并没有编译动作。

step4. 修改配置

vi ${HOME}/.gitolite.rc

主要是修改这一行,否则与之依赖的路径都是错的

$GL_ADMINDIR=$ENV{HOME} . “/.gitolite”;

step5. 新增gitolite用户

将step2中生成的XXX.pub公钥copy到服务器端,执行

gl-setup /pathto/XXX.pub 安装公钥到 ~/.ssh/authorized_keys

注意XXX.pub的XXX将作为gitolite用户名。

step6. clone gitolite-admin 到管理客户端

git clone git@server:gitolite-admin.git

step7. 配置用户权限

vi gitolite-admin/conf/gitolite.conf

git commit -a -m “update auth”

git push

misc. 新增版本库

最简单的办法就是在gitolite.conf 新增一个不存在的版本库的权限配置项,这样提交并上传到远程的时候,gitolite会自动创建一个指定名字的版本库。

也可以到服务器上手动执行,只不过这样的hook要自己去加了。

PART3-GITWEB:

1. 安装gitweb的二进制包。

2. 将/etc/gitweb.conf的内容替换为gitolite源码目录中的contrib/gitweb/gitweb.conf文件。注意设置好里面的home路径。

3. 确保/etc/httpd/conf.d/gitweb.conf有如下内容:

Alias /git /var/www/git
  Options +ExecCGI
  AddHandler cgi-script .cgi
  DirectoryIndex gitweb.cgi

4. 确保虚拟主机中有添加如下内容:
SuexecUserGroup git git

Advertisements

linux 下修改几种常用文件系统的label

最近磁盘和分区曓增,不上label肯定会发生混乱的,万一mkfs错了可就不好玩了。

VFAT32:

查看当前label: mlabel -s -i /dev/sdX ::

修改label:mlabel -i /dev/sdX :: <new label>

NTFS:

查看当前label:ntfslabel -f /dev/sdX

修改label:ntfslabel -f /dev/sdX <new label>

EXT:

查看当前label:e2label /dev/sdX

修改label:e2label /dev/sdX <new label>

reiserfs:

查看 :reiserfstune /dev/sdX |grep LABEL

设置:reiserfstune /dev/sdX -l <new label>

winXP rdp改配置

这个好像很少有人提到,rdp协议开了以后,默认梆定0.0.0.0:3389 ,如果想通过3389 传点其他数据就麻烦了,XP下也没有tscc.msc 。

那么就参考这篇文章去改注册表吧。

http://support.microsoft.com/kb/924927

Method 2: Manually edit the registry and add registry entries to enable listening for RDP requests

Important This section, method, or task contains steps that tell you how to modify the registry. However, serious problems might occur if you modify the registry incorrectly. Therefore, make sure that you follow these steps carefully. For added protection, back up the registry before you modify it. Then, you can restore the registry if a problem occurs. For more information about how to back up and restore the registry, click the following article number to view the article in the Microsoft Knowledge Base:

322756 How to back up and restore the registry in Windows

To manually edit the registry and add registry entries to enable listening for RDP requests, follow these steps:

  1. Click Start, click Run, type regedit, and then click OK.
  2. Locate and then click the following registry subkey:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces
  3. Right-click the GUID of the network adapter you want RDP to listen on, and then click Copy Key Name.
  4. Locate and then click the following registry subkey:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server
  5. On the Edit menu, click New, and then click Key.
  6. Type lanatable, and then press ENTER.
  7. Right-click the lanatable subkey, click New, and then click Key.
  8. Paste the GUID name that you copied in step 3. Delete any path information that is in the pasted text.
  9. Right-click this new key, click New, and then click DWORD Value.
  10. Type LanaId and then press ENTER.
  11. Double-click LanaId, type 1, and then click OK.
  12. Locate and then click the following registry subkey:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\Winstation\RDP-Tcp
  13. Double-click LanAdapter, change the value to 1, and then click OK.
  14. Exit Registry Editor.
  15. Restart the computer.

再谈linux input子系统

几年前我写过一篇《键码之旅》,现在看来其中不少错误和纰漏,是时候重新整理一下了。 输入子系统蓝图: snapshot1 input子系统会和其他各种子系统打交道,而打交道的这些子系统中肯定有输入类设备。比如usb接口键盘(需要usb子系统支持),蓝牙无线的鼠标键盘(需要蓝牙子系统支持)。圆角矩形表示设备驱动,椭圆形表示的是input设备驱动和event驱动。这样做的目的是将事件层与具体的输入设备分离,这样架构会更加灵活,易于维护。 注意:如果登录的是字符终端,那么用户态下的event接口就没有程序关注了,console/tty层直接从内核态获得keycode。所以当X11没有响应时,切到tty还有机会让键盘有反应。 接下来看一下相关的数据结构: 在这些输入设备中只有ps/2接口的算是比较正统的,原因如下: 1. PS/2接口本来就是为鼠标和键盘而生的,从serio设备相关代码都在drivers/input/serio目录下可以印证这个说法。而且像Nroll key 全键盘无冲突这类功能也只有在ps/2接口下才ok(原因主要是ps/2协议) 2. PS/2的键盘鼠标产生的中断是一个独占的中断,因为host端有一个i8042芯片(现在集成在super i/o芯片中)作为serio接口处理器来处理中断。对比usb的键盘鼠标,中断由usb host控制器产生,有些控制器会共享一个中断,每个控制器上的usb设备也会共享一个中断。 具体考古方面的问题可以看这篇文章:http://www.computer-engineering.org/ps2keyboard/snapshot3 ———————————————————————————————————————————————————————– snapshot4 PS/2 接口的键盘又称为“AT”键盘,“PC”键盘。这里就以原生的AT键盘为例,简述当按下一个键后会发生什么。

  1. 键盘内部的控制器扫描矩阵把键的动作编码成扫描码发送出去
  2. serio控制器(i8042)读取扫描码,并产生一个i8042中断
  3. i8042中断例程(drivers/input/serio/i8042.c:i8042_interrupt)中调用AT键盘的中断处理函数atkbd_intterupt(drivers/input/keyboard/atkbd.c:atkbd_interrupt),根据atkbd结构中的表,将scancode转换成keycode
  4. 转移到事件驱动的event handle继续处理
  5. 对于一般的按键,该keycode 会上传给console/tty层,drivers.char/keyboard.c:
    /* Add the keycode to flip buffer */
    tty_insert_flip_char(tty, keycode, 0);
    /* Schedule */
    con_schedule_flip(tty); 接下来这个keycode还要经过tty的转义(相当于OSI中表示层的概念),如果用的是bash,还要再经过bash转义(/etc/inputrc)
  6. 对于一些特殊的按键序列,比如sysrq或alt+ctrl+del 就要跳到其他处理流程里了

————————————————————————————————————————————————————————- 看一下相关的核心数据结构: struct input_dev {

    const char *name;     //名称                              
    const char *phys;  //设备在系统中的物理路径
    const char *uniq;  //设备唯一识别符
    struct input_id id; //设备ID,包含总线ID(PCI、USB)、厂商ID,与input_handler匹配的时会用到  

    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     //支持的所有事件类型  
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];   //支持的键盘事件  
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];   //支持的鼠标相对值事件  
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];   //支持的鼠标绝对值事件  
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];   //支持的其它事件类型  
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];   //支持的LED灯事件  
    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];   //支持的声效事件 
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];     //支持的力反馈事件  
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];     //支持的开关事件  

    unsigned int keycodemax;  //keycode表的大小
    unsigned int keycodesize;  //keycode表中元素个数
    void *keycode;  //设备的键盘表
    int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);//配置keycode表  
    int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);//获取keycode表  

    struct ff_device *ff;  

    unsigned int repeat_key;//保存上一个键值  
    struct timer_list timer;  

    int sync;  

    int abs[ABS_MAX + 1];             //绝对坐标上报的当前值  
    int rep[REP_MAX + 1];             //这个参数主要是处理重复按键,后面遇到再讲  
    unsigned long key[BITS_TO_LONGS(KEY_CNT)]; //按键有两种状态,按下和抬起,这个字段就是记录这两个状态。  
    unsigned long led[BITS_TO_LONGS(LED_CNT)];  
    unsigned long snd[BITS_TO_LONGS(SND_CNT)];  
    unsigned long sw[BITS_TO_LONGS(SW_CNT)];  

    int absmax[ABS_MAX + 1];           //绝对坐标的最大值  
    int absmin[ABS_MAX + 1];       //绝对坐标的最小值  
    int absfuzz[ABS_MAX + 1];            
    int absflat[ABS_MAX + 1];            
    //操作接口
    int (*open)(struct input_dev *dev);  
    void (*close)(struct input_dev *dev);  
    int (*flush)(struct input_dev *dev, struct file *file);  
    int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);  /* 一般为atkbd_event */

    struct input_handle *grab;         //当前使用的handle  

    spinlock_t event_lock;  
    struct mutex mutex;  

    unsigned int users;  
    int going_away;  

    struct device dev;  

    struct list_head    h_list;    //h_list是一个链表头,用来把handle挂载在这个上  
    struct list_head    node;      //这个node是用来连到input_dev_list上的  
};  
 // input_dev->evbit表示设备支持的事件类型,可以是下列值的组合
       #define EV_SYN           0x00  //同步事件
        #define EV_KEY           0x01 //绝对二进制值,如键盘或按钮
        #define EV_REL           0x02 //绝对结果,如鼠标设备
        #define EV_ABS           0x03 //绝对整数值,如操纵杆或书写板
        #define EV_MSC          0x04 //其它类
        #define EV_SW            0x05 //开关事件
        #define EV_LED          0x11 //LED或其它指示设备
        #define EV_SND         0x12 //声音输出,如蜂鸣器
        #define EV_REP         0x14 //允许按键自重复
        #define EV_FF             0x15 //力反馈
        #define EV_PWR        0x16 //电源管理事件
include/linux/input.h中定义了支持的类型
比如pc keyboard的evbit为0x120013(可以从/sys/class/input/input0/capabilities/ev获知)代表他有EV_KEY,EV_SYN,EV_MSC,EV_LED,EV_REP这些事件类型。


struct input_handler {  
    void *private;  

    void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);  
    int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);  
    void (*disconnect)(struct input_handle *handle);  
    void (*start)(struct input_handle *handle);  

    const struct file_operations *fops;  
    int minor;                               //次设备号  
    const char *name;  

    const struct input_device_id *id_table;  
    const struct input_device_id *blacklist;  

    struct list_head    h_list;    //h_list是一个链表头,用来把handle挂载在这个上  
    struct list_head    node;      //这个node是用来连到input_handler_list上的  
};  

struct input_handle {  

    void *private;  

    int open;  
    const char *name;  

    struct input_dev *dev;              //指向input_dev  
    struct input_handler *handler;      //指向input_handler  

    struct list_head    d_node;     //连到input_dev的h_list上  
    struct list_head    h_node;     //连到input_handler的h_list上  
};
struct atkbd {
	struct ps2dev ps2dev;
	struct input_dev *dev;

	/* Written only during init */
	char name[64]; //nomally is "AT Translated Set 2 keyboard"
	char phys[32];

	unsigned short id;
	unsigned short keycode[ATKBD_KEYMAP_SIZE]; //在atkbd_set_keycode_table中初始化
	DECLARE_BITMAP(force_release_mask, ATKBD_KEYMAP_SIZE);
	unsigned char set;
	bool translated; //如果是keycode set2则为true
	bool extra;
	bool write;
	bool softrepeat;
	bool softraw;
	bool scroll;
	bool enabled;

	/* Accessed only from interrupt */
	unsigned char emul;
	bool resend;
	bool release;
	unsigned long xl_bit;
	unsigned int last;
	unsigned long time;
	unsigned long err_count;

	struct delayed_work event_work;
	unsigned long event_jiffies;
	unsigned long event_mask;

	/* Serializes reconnect(), attr->set() and event work */
	struct mutex mutex;
};

这三个结构通过链表相互关联起来 list1 list2 看得出,intput_handle仿佛只是一个桥梁,注意它还有一个private成员变量。 —————————————————————————————————————————————————————- 下面看一下设备驱动,也就是从硬中断开始:

static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
				   unsigned int flags)
{
	struct atkbd *atkbd = serio_get_drvdata(serio);
	struct input_dev *dev = atkbd->dev;
	unsigned int code = data;
	int scroll = 0, hscroll = 0, click = -1;
	int value;
	unsigned short keycode;

	dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, flags); //data就是scancode

#if !defined(__i386__) && !defined (__x86_64__)
	if ((flags & (SERIO_FRAME | SERIO_PARITY)) && (~flags & SERIO_TIMEOUT) && !atkbd->resend && atkbd->write) {
		dev_warn(&serio->dev, "Frame/parity error: %02x\n", flags);
		serio_write(serio, ATKBD_CMD_RESEND);
		atkbd->resend = true;
		goto out;
	}

	if (!flags && data == ATKBD_RET_ACK)
		atkbd->resend = false;
#endif

	if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_ACK))
		if  (ps2_handle_ack(&atkbd->ps2dev, data))
			goto out;

	if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_CMD))
		if  (ps2_handle_response(&atkbd->ps2dev, data))
			goto out;
	/* atkbd 初始化,注销,重连时可能会disabled */
	if (!atkbd->enabled)
		goto out;

	/* 往上层上报一个RAW code事件 */
	input_event(dev, EV_MSC, MSC_RAW, code);

	/* 只有OQO Model 01+ (ZEPTO) 这种类型的键盘才会走这个条件分支,相关内容看dmi_check_system */
	if (atkbd_platform_scancode_fixup)
		code = atkbd_platform_scancode_fixup(atkbd, code);

	if (atkbd->translated) {

		if (atkbd->emul || atkbd_need_xlate(atkbd->xl_bit, code)) {
			atkbd->release = code >> 7;
			code &= 0x7f;
		}

		if (!atkbd->emul)
			atkbd_calculate_xl_bit(atkbd, data);
	}
	/* 一些特殊的keycode处理分支流程 */
	switch (code) {
	case ATKBD_RET_BAT:
		atkbd->enabled = false;
		serio_reconnect(atkbd->ps2dev.serio);
		goto out;
	case ATKBD_RET_EMUL0:
		atkbd->emul = 1;
		goto out;
	case ATKBD_RET_EMUL1:
		atkbd->emul = 2;
		goto out;
	case ATKBD_RET_RELEASE:
		atkbd->release = true;
		goto out;
	case ATKBD_RET_ACK:
	case ATKBD_RET_NAK:
		if (printk_ratelimit())
			dev_warn(&serio->dev,
				 "Spurious %s on %s. "
				 "Some program might be trying access hardware directly.\n",
				 data == ATKBD_RET_ACK ? "ACK" : "NAK", serio->phys);
		goto out;
	case ATKBD_RET_ERR:
		atkbd->err_count++;
		dev_dbg(&serio->dev, "Keyboard on %s reports too many keys pressed.\n",
			serio->phys);
		goto out;
	}
	/* 为2.4内核兼容? */
	code = atkbd_compat_scancode(atkbd, code);

	if (atkbd->emul && --atkbd->emul)
		goto out;

	/* 将scancode转换为keycode */
	keycode = atkbd->keycode[  code  ];

	/* 向上层上报一个scancode */
	if (keycode != ATKBD_KEY_NULL)
		input_event(dev, EV_MSC, MSC_SCAN, code);

	switch (keycode) {
	case ATKBD_KEY_NULL:
		break;
	case ATKBD_KEY_UNKNOWN:
		dev_warn(&serio->dev,
			 "Unknown key %s (%s set %d, code %#x on %s).\n",
			 atkbd->release ? "released" : "pressed",
			 atkbd->translated ? "translated" : "raw",
			 atkbd->set, code, serio->phys);
		dev_warn(&serio->dev,
			 "Use 'setkeycodes %s%02x ' to make it known.\n",
			 code & 0x80 ? "e0" : "", code & 0x7f);
		input_sync(dev);
		break;
	case ATKBD_SCR_1:
		scroll = 1;
		break;
	case ATKBD_SCR_2:
		scroll = 2;
		break;
	case ATKBD_SCR_4:
		scroll = 4;
		break;
	case ATKBD_SCR_8:
		scroll = 8;
		break;
	case ATKBD_SCR_CLICK:
		click = !atkbd->release;
		break;
	case ATKBD_SCR_LEFT:
		hscroll = -1;
		break;
	case ATKBD_SCR_RIGHT:
		hscroll = 1;
		break;
	default:
/* 设置按键的按下状态(value=0)释放状态(value=1)和重复状态(value=2)*/
		if (atkbd->release) {
			value = 0;
			atkbd->last = 0;
		} else if (!atkbd->softrepeat && test_bit(keycode, dev->key)) {
			/* Workaround Toshiba laptop multiple keypress */
			value = time_before(jiffies, atkbd->time) && atkbd->last == code ? 1 : 2;
		} else {
			value = 1;
			atkbd->last = code;
			atkbd->time = jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]) / 2;
		}

		/* 向上层上报一个keycode事件 */
		input_event(dev, EV_KEY, keycode, value);
		/* 向上层报告同步事件 */
		input_sync(dev);

		if (value && test_bit(code, atkbd->force_release_mask)) {
			input_report_key(dev, keycode, 0);
			input_sync(dev);
		}
	}

	if (atkbd->scroll) {
		if (click != -1)
			input_report_key(dev, BTN_MIDDLE, click);
		input_report_rel(dev, REL_WHEEL,
				 atkbd->release ? -scroll : scroll);
		input_report_rel(dev, REL_HWHEEL, hscroll);
		input_sync(dev);
	}

	atkbd->release = false;
out:
	return IRQ_HANDLED;
}

该中断处理函数的核心就是调用input_event上报各类事件:

void input_event(struct input_dev *dev,
		 unsigned int type, unsigned int code, int value)
{
	unsigned long flags;
/* 判断是否该设备支持type指定的EV类型 */
	if (is_event_supported(type, dev->evbit, EV_MAX)) {
		spin_lock_irqsave(&dev->event_lock, flags);

/* 为内核随机数产生器提供随机因子 */
		add_input_randomness(type, code, value);
		input_handle_event(dev, type, code, value);
		spin_unlock_irqrestore(&dev->event_lock, flags);
	}
}

顺着向上层看一下input_handle_event,该函数的作用是根据type(EV bit)和code(EV bit对应的下属bit)向设备和事件处理句柄分发事件。

static void input_handle_event(struct input_dev *dev,
			       unsigned int type, unsigned int code, int value)
{
	int disposition = INPUT_IGNORE_EVENT;
	switch (type) {

	case EV_SYN:
		switch (code) {
		case SYN_CONFIG:
			disposition = INPUT_PASS_TO_ALL;
			break;
		case SYN_REPORT:
			if (!dev->sync) {
				dev->sync = true;
				disposition = INPUT_PASS_TO_HANDLERS;
			}
			break;
		break;

	case EV_KEY:
		if (is_event_supported(code, dev->keybit, KEY_MAX) &&
		    !!test_bit(code, dev->key) != value) {
			if (value != 2) {
				__change_bit(code, dev->key);
				if (value)
					input_start_autorepeat(dev, code);
				else
					input_stop_autorepeat(dev);
			}
			disposition = INPUT_PASS_TO_HANDLERS;
		}
		break;

	case EV_REL:
		if (is_event_supported(code, dev->relbit, REL_MAX) && value)
			disposition = INPUT_PASS_TO_HANDLERS;
		break;

	case EV_MSC:
		if (is_event_supported(code, dev->mscbit, MSC_MAX))
			disposition = INPUT_PASS_TO_ALL;
		break;

	if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
		dev->sync = false;
/* 将事件上报给设备处理 */
	if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
		dev->event(dev, type, code, value);
/* 将事件上报给事件处理句柄 */
	if (disposition & INPUT_PASS_TO_HANDLERS)
		input_pass_event(dev, type, code, value);
}

将事件上报给设备处理意味着将设备产生的keycode回流给设备本身,比如capslock键将会使LED点亮,看一下atbkd对应的event:atkbd_event函数:

static int atkbd_event(struct input_dev *dev,
			unsigned int type, unsigned int code, int value)
{
	struct atkbd *atkbd = input_get_drvdata(dev);

	if (!atkbd->write)
		return -1;

	switch (type) {

	case EV_LED:
		atkbd_schedule_event_work(atkbd, ATKBD_LED_EVENT_BIT);
		return 0;

	case EV_REP:
		if (!atkbd->softrepeat)
			atkbd_schedule_event_work(atkbd, ATKBD_REP_EVENT_BIT);
		return 0;

	default:
		return -1;
	}
}

将事件上报给事件处理句柄意味这事件将真正到达事件处理层,对于PC keyboard的event handle就是sysrq,kbd,eventX,这些handle对应的handler分别是sysrq_handler,kbd_handler,evdev_handler。每次上报一个事件,都会由input_pass_event函数去遍历这些event handle再有这些event handle 找到对应的handler,最后调用handler->event()函数对事件进行最终的处理。 回看atkbd_interrupt的三次input_event调用: 第一次type为EV_MSC,code为MSC_RAW,但有/sys/class/input/input0/capabilities/msc 为0x10可知(第4位为0),一般PC keyboard都不支持上报RAW类型事件。也就是说对于那些对event设备感兴趣的应用程序而言是无法从这里获得RAW scancode的。 第二次type为EV_MSC,code为MSC_SCAN 正好是PC keyboard唯一支持的EV_MSC事件类型,所以可以在此上报scancode 事件。其实从代码里可以看到RAW scancode和scancode 区别在于前者可以多几种scancode。 第三次type为EV_KEY,code为scancode,这才算是上报一次普通的按键事件。 最后一路跟踪到了kbd_handler,里面将调用kbd_event进行最终的处理。另外两个,sysrq_event和evdev_event不再讲了,前者处理sysrq特殊按键组合的事件,后者涉及evdev client和用户层挂接的问题。

static void kbd_event(struct input_handle *handle, unsigned int event_type,
		      unsigned int event_code, int value)
{
	/* We are called with interrupts disabled, just take the lock */
	spin_lock(&kbd_event_lock);
/* 对于PC keyboard 这个分支是废的 */
	if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev))
		kbd_rawcode(value);
	if (event_type == EV_KEY)
		kbd_keycode(event_code, value, HW_RAW(handle->dev));

	spin_unlock(&kbd_event_lock);
/* 会唤醒kbd_bh执行一些LED的操作 */
	tasklet_schedule(&keyboard_tasklet);
	do_poke_blanked_console = 1;
/* 会唤醒console_callback执行一些切换操作 */
	schedule_console_callback();
}

对于一个常规按键,kbd_event会在整个流程中被调用两次,第一次是event_type=EV_MSC ,event_code=MSC_SCAN ,这次其实是会被忽略。另外HW_RAW(handle->dev) 这个宏对于PC keyboard恒为0,所以再次重复一遍,PC keyboard永远也不会将raw scancode上报处理。 第二次是event_type=EV_KEY,event_code=keycode,这才是需要关心的。

static void kbd_keycode(unsigned int keycode, int down, int hw_raw)
{
	struct vc_data *vc = vc_cons[fg_console].d;
	unsigned short keysym, *key_map;
	unsigned char type;
	bool raw_mode;
	struct tty_struct *tty;
	int shift_final;
	struct keyboard_notifier_param param = { .vc = vc, .value = keycode, .down = down };
	int rc;

	tty = vc->port.tty;

	if (tty && (!tty->driver_data)) {
		/* No driver data? Strange. Okay we fix it then. */
		tty->driver_data = vc;
	}
/* 找到vc对应的kbd */
	kbd = kbd_table + vc->vc_num;

#ifdef CONFIG_SPARC
	if (keycode == KEY_STOP)
		sparc_l1_a_state = down;
#endif

/* donw=1 表示按下,=2 表示键重复 */
	rep = (down == 2);

/* 如果kbd_mode设置了RAW模式,并且硬件不支持上报raw scancode 则需要将keycode转换为raw scancode(真折腾)再放到vc的输入队列中去*/
	raw_mode = (kbd->kbdmode == VC_RAW);
	if (raw_mode && !hw_raw)
		if (emulate_raw(vc, keycode, !down << 7))
			if (keycode < BTN_MISC && printk_ratelimit())
 				pr_warning("can't emulate rawmode for keycode %d\n", keycode); 	/* This code has to be redone for some non-x86 platforms */ 
	if (down == 1 && (keycode == 0x3c || keycode == 0x01)) { 
		/* F2 and ESC on PC keyboard */
 		if (splash_verbose())
 			return;
 	} 
#ifdef CONFIG_SPARC
 	if (keycode == KEY_A && sparc_l1_a_state) {
 		sparc_l1_a_state = false;
 		sun_do_break();
 	} 
#endif
/* 如果kbd_mode是ascii模式,则进行另外一些转换后再放入vc的输入队列 */
 	if (kbd->kbdmode == VC_MEDIUMRAW) {
		/*
		 * This is extended medium raw mode, with keys above 127
		 * encoded as 0, high 7 bits, low 7 bits, with the 0 bearing
		 * the 'up' flag if needed. 0 is reserved, so this shouldn't
		 * interfere with anything else. The two bytes after 0 will
		 * always have the up flag set not to interfere with older
		 * applications. This allows for 16384 different keycodes,
		 * which should be enough.
		 */
		if (keycode < 128) {
			put_queue(vc, keycode | (!down << 7));
		} else {
			put_queue(vc, !down << 7);
 			put_queue(vc, (keycode >> 7) | 0x80);
			put_queue(vc, keycode | 0x80);
		}
		raw_mode = true;
	}

	if (down)
		set_bit(keycode, key_down);
	else
		clear_bit(keycode, key_down);

	if (rep &&
	    (!vc_kbd_mode(kbd, VC_REPEAT) ||
	     (tty && !L_ECHO(tty) && tty_chars_in_buffer(tty)))) {
		/*
		 * Don't repeat a key if the input buffers are not empty and the
		 * characters get aren't echoed locally. This makes key repeat
		 * usable with slow applications and under heavy loads.
		 */
		return;
	}

	param.shift = shift_final = (shift_state | kbd->slockstate) ^ kbd->lockstate;
	param.ledstate = kbd->ledflagstate;

/* key_map指的是ctrl,shift,capslock状态组合出来的值,实际也就决定了实际将要使用的键盘布局 */
	key_map = key_maps[shift_final];

	rc = atomic_notifier_call_chain(&keyboard_notifier_list,
					KBD_KEYCODE, &param);
	if (rc == NOTIFY_STOP || !key_map) {
		atomic_notifier_call_chain(&keyboard_notifier_list,
					   KBD_UNBOUND_KEYCODE, &param);
		do_compute_shiftstate();
		kbd->slockstate = 0;
		return;
	}

	if (keycode < NR_KEYS) 	
/* 获得keycode对应的keysym,参看drivers/tty/vt/defkeymap.c */	
           keysym = key_map[keycode]; 	
        else if (keycode >= KEY_BRL_DOT1 && keycode <= KEY_BRL_DOT8)
		keysym = U(K(KT_BRL, keycode - KEY_BRL_DOT1 + 1));
	else
		return;

	type = KTYP(keysym);
/* type是keysym的高8位值 */
	if (type < 0xf0) {
		param.value = keysym;
		rc = atomic_notifier_call_chain(&keyboard_notifier_list,
						KBD_UNICODE, &param);
		if (rc != NOTIFY_STOP)
			if (down && !raw_mode)
				to_utf8(vc, keysym);
		return;
	}

	type -= 0xf0;
/* 如果是26个字母会走这个分支, 注意type会重新置0*/
	if (type == KT_LETTER) {
		type = KT_LATIN;
/* 根据capslock重新再查一次key_map */
		if (vc_kbd_led(kbd, VC_CAPSLOCK)) {
			key_map = key_maps[shift_final ^ (1 << KG_SHIFT)];
 			if (key_map)
 				keysym = key_map[keycode];
 		}
 	}
 	param.value = keysym;

/* 通知回调函数keyboard_notifier_call */
 	rc = atomic_notifier_call_chain(&keyboard_notifier_list,	KBD_KEYSYM, &param);
/* 通知循环停止并且键盘事件被丢弃 */
 	if (rc == NOTIFY_STOP)
 		return;
 	if ((raw_mode || kbd->kbdmode == VC_OFF) && type != KT_SPEC && type != KT_SHIFT)
		return;
/* 对于字母键将会调用k_self处理,keysym将会放到vc的输入队列 */
	(*k_handler[type])(vc, keysym & 0xff, !down);

	param.ledstate = kbd->ledflagstate;
	atomic_notifier_call_chain(&keyboard_notifier_list, KBD_POST_KEYSYM, &param);

	if (type != KT_SLOCK)
		kbd->slockstate = 0;
}

看一下k_self 函数,也就是k_unicode函数:
static void k_unicode(struct vc_data *vc, unsigned int value, char up_flag)
{
	if (up_flag)
		return;		/* no action, if this is a key release */

	if (diacr)
		value = handle_diacr(vc, value);

	if (dead_key_next) {
		dead_key_next = false;
		diacr = value;
		return;
	}
/* 如果kbd_mode是utf8模式还要进行一次转换 */
	if (kbd->kbdmode == VC_UNICODE)
		to_utf8(vc, value);
	else {
/* 其他kbd_mode 都转换到8bit 字节模式 */
		int c = conv_uni_to_8bit(value);
		if (c != -1)
/* 放入vc的输入队列 */
			put_queue(vc, c);
	}
}

最后说一下,vc的输入队列是由work_queue处理的,所以总的来说内核对于vc下的键盘处理优先级相当低。

[转]bash空格那点事

空格,一个看不见的字符,很不起眼,也正由于不起眼,很多人经常忽略它,导致代码出错,却还找不着北。这里,我们来聊聊bash中空格的那点事。

先了解下bash中什么时候该用空格,什么时候不该用。

    1. 等号赋值两边不能有空格
    2. 命令与选项之间需要空格
    3. 管道两边空格可有可无

我们来看看常见的问题
1. 赋值时等号两边或者只有左边多了空格

1
2
3
4
5
6
7
8
9
10
11
12
igi@gentoo ~ $ var1 = test
bash: var1: command not found
igi@gentoo ~ $ echo ${var1:?error}
bash: var1: error
igi@gentoo ~ $ echo ${var1?error}
bash: var1: error
igi@gentoo ~ $ var2 =test
bash: var2: command not found
igi@gentoo ~ $ echo ${var2:?error}
bash: var2: error
igi@gentoo ~ $ echo ${var2?error}
bash: var2: error

这里我用了bash的变量扩展,${var1:?error}当var1为unset或null(未定义或空)时, 报指定错误; ${var1?error}当var1为unset时,报指定错误 。从执行结果来看,如果等号左边有空格,则变量名当成命令执行,结果报command not found,变量没有被赋值

2. 赋值时等号左边没有空格,右边有空格(这种情况有点特别,你会发现两种情况)

1
2
3
igi@gentoo ~ $ var= test
igi@gentoo ~ $ var= nocmd
bash: nocmd: command not found

同样是等号右边有空格,第一条命令没报错,而第二条报错了。
这是因为shell中有这么一种执行命令的方式: var=string command
命令command将得到变量var的值(至于在命令执行后,变量var的值是否保留下来,bash4中没有保留,但我在dash中发现时保留下来的,不同的shell对这个的处理不同), 由于test是个命令,而nocmd不是,所以报了command not found.

1
2
3
igi@gentoo ~ $ var=newtest eval echo \$var
newtest
igi@gentoo ~ $ echo $var

注意: 这里我使用了eval, 是想避免在第一次解析时$var被替换成空字符串, 不然就会出现下面的情况(下面是错误的测试方法,在echo还没执行时,$var已经被替换成空字符串)

1
2
3
igi@gentoo ~ $ var=newtest echo $var
igi@gentoo ~ $ echo $var

到这里,相信大家都明白了吧, 对于等号赋值,左右两边不可以有空格,虽然右边有空格不一定报错,但那绝对不是你想要的结果。

3. 命令和选项之间必须有空格
这个似乎大家都明白,为何我还这么罗嗦呢?说到这里,不得不提一下一个非常特别的命令: [ 命令(你没看错,是[ ), 也就是test命令(当然bash中,这是个内置命令,但在这里不影响
我们的理解)。或许你会觉得[命令眼熟,没错,我保证你见过它,来看看下面的例子

1
2
3
4
5
igi@gentoo ~ $ if "abc" "abc" ]; then echo ‘they are the same’; fi
they are the same
igi@gentoo ~ $ type -a [
[ is a shell builtin
[ is /usr/bin/[

想起来了吧?[命令经常用到if判断中,当然也有人喜欢这么写

1
2
3
4
5
igi@gentoo ~ $ [ "abc" "cba" ] || echo ‘they are not the same’
they are not the same
igi@gentoo ~ $ type -a [
[ is a shell builtin
[ is /usr/bin/[

[ 命令正名叫test命令,它们两者几乎一样,为什么不是完全一样?来看看这个

1
2
3
4
5
6
igi@gentoo ~ $ [ "abc" "cba"
bash: [: missing `]‘
igi@gentoo ~ $ [ "abc" "cba" ]
igi@gentoo ~ $ test "abc" "cba" ]
bashtest: too many arguments
igi@gentoo ~ $ test "abc" "cba"

清晰了吧,用[命令时,你必须给它个尾巴], 用test命令时,就不能加个尾巴。尾巴]是[最后一个参数,不可缺少的参数, 代表[命令的结束

扯了这么多,那到底这个和空格有毛关系?说这些,是先让大家明白: [在shell中是个命令,它左右必须有空格!]是[的最后不可缺少的参数,它两边也需要空格(虽然有些命令的参数能连一起,例如ps, 但[命令不行,它的参数之间必须有空格)。让我们看看关于[常见的错误

a. if 与 [ 之间缺少空格

1
2
3
4
5
6
7
8
igi@gentoo ~ $ if"$HOME" "/home/igi"];then echo 'equal'fi
bash: syntax error near unexpected token `then'
igi@gentoo ~ $ if"$HOME" "/home/igi" ];then echo 'equal'fi
bash: syntax error near unexpected token `then'
igi@gentoo ~ $ if["$HOME" "/home/igi"];then echo 'equal'fi
bash: syntax error near unexpected token `then'
igi@gentoo ~ $ if["$HOME" "/home/igi" ];then echo 'equal'fi
bash: syntax error near unexpected token `then'

语法分析错误,很明显,if[ 对于bash来说,不知道是什么鬼东西

b. [与后面的参数之间缺少空格

1
2
3
4
igi@gentoo ~ $ if ["$HOME" "/home/igi" ];then echo 'equal'fi
bash: [/home/igi: No such file or directory
igi@gentoo ~ $ if ["$HOME" "/home/igi"];then echo 'equal'fi
bash: [/home/igi: No such file or directory

[“$HOME” 对于bash来说,也不知道是什么鬼东西

c. [ ] 之间的参数之间缺少空格

1
2
3
4
igi@gentoo ~ $ if "abc"="abc" ]; then echo 'equal'fi
equal
igi@gentoo ~ $ if "abc"="cba" ]; then echo 'equal'fi
equal

第一条命令似乎是对的(实际上是正巧而已),看看第二条命令”abc” 和 “cba”明显不同,但却判断为相同。这是因为参数之间缺少了空格,被[命令认为内部是个值而已。看看下面的命令,你就会释然

1
2
3
4
5
6
igi@gentoo ~ $ if [ 0 ]; then echo 'equal'fi
equal
igi@gentoo ~ $ if "1" ]; then echo 'equal'fi
equal
igi@gentoo ~ $ if "" ]; then echo 'equal'fi
igi@gentoo ~ $ if [ ]; then echo 'equal'fi

在[ ] 内部,如果只有一个值(那些因为缺少了空格而连一起的也算),不是空字符串就为真。所以在[ ] 之间的参数,也要两边有空格,而不能堆一起

d. 参数和尾巴]之间缺少空格
这个就不罗嗦了,尾巴]也是[命令的参数,如同上面所讲,参数之间必须有空格

扯了这么多[命令与空格的事,但有些时候,缺了空格却能正确运行, 当然这只是你好运, 一起来看看

1
2
3
4
5
igi@gentoo ~ $ var=' abc'
igi@gentoo ~ $ if [$var = "abc" ];then echo 'equal'fi
equal
igi@gentoo ~ $ if ["$var" "abc" ];then echo 'equal'fi
bash: [ abc: command not found

之前Bash引号那点事提到过,双引号包围起来的是一个整体,而没双引号的时候,字符串前后的空格或制表符都被切开。如果恰巧你遇到了或者你故意要丢弃字符串前后的空格或制表符,那也不是不可能, 但非常不建议你这么写,你的代码将是非常脆弱的。

或者你该加的空格都加了,但还是报错,这也可能和缺少双引号有关。这样的情况很普遍,最后再看看

1
2
3
4
5
6
7
8
9
igi@gentoo ~ $ var=''
igi@gentoo ~ $ if "$var" "abc" ];then echo 'equal'fi
igi@gentoo ~ $ if [ $var = "abc" ];then echo 'equal'fi
bash: [: =: unary operator expected
igi@gentoo ~ $ dvar='a b c'
igi@gentoo ~ $ if [ $dvar = "a b c" ];then echo 'equal'fi
bash: [: too many arguments
igi@gentoo ~ $ if "$dvar" "a b c" ];then echo 'equal'fi
equal

我再罗嗦一次,不要轻易省略双引号。很清楚了吧?如果你还不明白,
请读读Bash引号那点事

最后,对于管道两边可有可无的空格,就不扯淡了,因为没遇到有人对此有疑惑.

[转]bash星号那点事

星号,这是个神奇的符号,在bash中,星号也充满魔力,它是通配符之一。
简单列举下星号的功能,被称为万能字符不是没有理由的.
1)文件名匹配: 默认情况下匹配所有非隐藏文件(即非.开头到文件)
2)字符串匹配: 匹配任意字符
3)$*匹配所有参数
4)${array[*]},表示了所有数组元素
5)乘法运算
6)** 冪运算
7)** bash4中提供的更牛b的文件名匹配,包含递归功能
接下我们来见识下它强大到魔力:
1)文件名匹配:

1
2
3
4
5
6
igi@gentoo ~ $ ls
a  b  c  d
igi@gentoo ~ $ ls -A
a  b  c  d  .t  .u  .v  .w  .x  .y
igi@gentoo ~ $ echo *
a b c d

我们可以看到,*号匹配了所有非隐藏文件名,如果要匹配所有文件名(包括隐藏文件),可以打开dotglob开关

1
2
3
4
5
6
7
igi@gentoo ~ $ ls -A
a  b  c  d  .t  .u  .v  .w  .x  .y
igi@gentoo ~ $ echo *
a b c d
igi@gentoo ~ $ shopt -s dotglob
igi@gentoo ~ $ echo *
a b c d .t .u .v .w .x .y

如果只是匹配隐藏文件呢?可别想得太复杂了哦

1
2
3
4
igi@gentoo ~ $ ls -a
.  ..  a  b  c  d  .t  .u  .v  .w  .x  .y
igi@gentoo ~ $ echo .*
. .. .t .u .v .w .x .y

需要注意到是,.*会把当前目录下到.目录和..目录也匹配进去

2)字符串匹配:这一般用在case语句、字符串截取中

1
2
3
4
5
6
7
8
9
10
11
12
igi@gentoo ~ $ case "abc" in
> a)
>   echo 'a'
>   ;;
> a*)
>   echo 'a*'
>   ;;
> esac
a*
igi@gentoo ~ $ var='abc123'
igi@gentoo ~ $ echo "${var%c*}"
ab

可以看到*号匹配了任意字符,在这里,先提醒一下,这里用的是模式匹配,而不是正则(正则与模式匹配不同,以后到文章中,将会仔细对比这两者到区别)

3)$*表示所有参数

1
2
3
4
5
6
7
8
9
igi@gentoo ~ $ foo() { for i in $*; do echo "var: $i"; done; }
igi@gentoo ~ $ foo a b "c cc"
var: a
var: b
var: c
var: cc
igi@gentoo ~ $ foo() { for i in "$*"; do echo "var: $i"; done; }
igi@gentoo ~ $ IFS="|" foo a b "c cc"
var: a|b|c cc

最后到例子中,我设置了新到IFS变量,只是为了让大家更清晰到看到,bash是如何对待”$*”的,bash用IFS变量,把所有参数拼成一个字符串,这就是”$*”
这里先说下$@和”$@”,在没有用双引号包围时,$@和$*一样表示了 $1 $2 $3 …
而”$@” 则与”$*”不同,”$@”表示了 “$1″ “$2″ “$3″ …(注意这里到双引号,双引号中的字符串是一个整体),似乎有点不明白,看看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
igi@gentoo ~ $ foo() { for i in $*;do echo "var: $i";done;}
igi@gentoo ~ $ foo a b "c cc"
var: a
var: b
var: c
var: cc
igi@gentoo ~ $ foo() { for i in $@;do echo "var: $i";done;}
igi@gentoo ~ $ foo a b "c cc"
var: a
var: b
var: c
var: cc
igi@gentoo ~ $ foo() { for i in "$*";do echo "var: $i";done;}
igi@gentoo ~ $ foo a b "c cc"
var: a b c cc
igi@gentoo ~ $ foo() { for i in "$@";do echo "var: $i";done;}
igi@gentoo ~ $ foo a b "c cc"
var: a
var: b
var: c cc

4)${array[*]}表示所有数组元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
igi@gentoo ~ $ array=( a b "c cc" )
igi@gentoo ~ $ for i in ${array[*]};do echo "array: $i"; done
array: a
array: b
array: c
array: cc
igi@gentoo ~ $ for i in ${array[@]};do echo "array: $i"; done
array: a
array: b
array: c
array: cc
igi@gentoo ~ $ for i in "${array[*]}";do echo "array: $i"; done
array: a b c cc
igi@gentoo ~ $ for i in "${array[@]}";do echo "array: $i"; done
array: a
array: b
array: c cc

细心的你应该不难看出,这和$*是一样的,我就不罗嗦了。

5、6)*号乘法运算, **冪运算

1
2
3
4
5
6
7
8
9
10
11
12
igi@gentoo ~ $ ((num=3*4))
igi@gentoo ~ $ echo $num
12
igi@gentoo ~ $ let num=3*3
igi@gentoo ~ $ echo $num
9
igi@gentoo ~ $ ((num=2**4))
igi@gentoo ~ $ echo $num
16
igi@gentoo ~ $ let num=2**2
igi@gentoo ~ $ echo $num
4

乘法运算和冪运算应该很容易理解吧。

7)bash4中更牛B的通配符**

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
igi@gentoo ~/test $ tree
.
├── a
│   ├── 1
│   ├── 2
│   ├── 3
│   ├── 4
│   └── 5
└── c
    ├── 2.txt
    ├── 3.txt
    ├── 4.txt
    └── dir
3 directories, 8 files
igi@gentoo ~/test $ shopt globstar
globstar        off
igi@gentoo ~/test $ echo **
a c
igi@gentoo ~/test $ echo **/
a/ c/
igi@gentoo ~/test $ echo *
a c
igi@gentoo ~/test $ echo **
a c
igi@gentoo ~/test $ echo */
a/ c/
igi@gentoo ~/test $ echo **/
a/ c/

默认情况下,globstar是关闭的,也就是**与*是一样的,我们来看看打开globstar后是怎么个牛b法?

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
igi@gentoo ~/test $ tree
.
├── a
│   ├── 1
│   ├── 2
│   ├── 3
│   ├── 4
│   └── 5
└── c
    ├── 2.txt
    ├── 3.txt
    ├── 4.txt
    └── dir
3 directories, 8 files
igi@gentoo ~/test $ shopt -s globstar
igi@gentoo ~/test $ shopt globstar
globstar        on
igi@gentoo ~/test $ echo *
a c
igi@gentoo ~/test $ echo **
a a/1 a/2 a/3 a/4 a/5 c c/2.txt c/3.txt c/4.txt c/dir
igi@gentoo ~/test $ echo */
a/ c/
igi@gentoo ~/test $ echo **/
a/ c/ c/dir/

可以看到打开globstar后,**递归的匹配了所有文件和目录, 如果**后面跟着/(即是**/),则只匹配目录。
问题来了,如果递归显示以.txt结尾到文件,是不是**.txt? 非也,来看看

1
2
3
4
5
6
7
8
9
10
11
12
igi@gentoo ~/test $ shopt -s globstar
igi@gentoo ~/test $ shopt globstar
globstar        on
igi@gentoo ~/test $ find . -name '*.txt'
./a.txt
./c/3.txt
./c/2.txt
./c/4.txt
igi@gentoo ~/test $ echo **.txt
a.txt
igi@gentoo ~/test $ echo **/*.txt
a.txt c/2.txt c/3.txt c/4.txt

看到了吧,**.txt是无法递归的,而**/*.txt就可以了,同理, foo**这样也不行,**/foo*这样到才可以。
这个功能是bash4才有的哦,使用之前,先确认下你到bash版本。

接下来,我们来看看常见到错误
1)用单引号或双引号包围了星号
在’Bash引号的那点事‘中我讲过,单引号中到字符都只有字符的原本意义,而双引号中,*号也是它本身到字符意义,在单双引号中,*号将失去它到魔力,这里就不再罗嗦
如果在双引号中,$*将表示一个由所有参数拼接而成到字符串,上面已经提到过。

2)没有考虑星号匹配不到任何文件的情况
如果指定到目录下没有任何文件时,使用星号匹配,会有啥现象?

1
2
3
4
igi@gentoo ~ $ rm -rf *
igi@gentoo ~ $ ls
igi@gentoo ~ $ echo *
*

看到了吧,如果星号匹配不到任何文件时,它变回了自己原本到意思(就是字符*)

1
2
3
4
5
6
7
8
9
10
11
igi@gentoo ~ $ ls
a  b  c  d
igi@gentoo ~ $ for i in *;do echo "file: $i" ;done
file: a
file: b
file: c
file: d
igi@gentoo ~ $ rm -rf *
igi@gentoo ~ $ ls
igi@gentoo ~ $ for i in *;do echo "file: $i" ;done
file: *

只是一个echo时,似乎没多大问题,但你想想,如果你在for中对文件做某些操作,如果匹配不到,变成对*号文件进行操作,我想结果肯定不是你要的,多数情况下,我们想要是,匹配不到则不进行任何操作,有没有办法?办法很多,例如你可以先做个判断,bash中,有个nullglob, 利用它,我们可以直接达到我们要到效果。

1
2
3
4
5
6
7
8
igi@gentoo ~ $ rm -rf *
igi@gentoo ~ $ shopt nullglob
nullglob        off
igi@gentoo ~ $ for i in *;do echo "file: $i"; done
file: *
igi@gentoo ~ $ shopt -s nullglob
igi@gentoo ~ $ for i in *;do echo "file: $i"; done
igi@gentoo ~ $

当然你也可以设置failglob,使得匹配不到文件时报错,这里就不再罗嗦了。

3)混淆模式匹配和正则
先申明,这两者是不同到,这里不打算详细阐述,只列举些常见错误做法

1
2
3
4
5
6
igi@gentoo ~/test $ ls
foo-a-log  foo-b-log  foo-c-log  zoo-a-log  zoo-b-log  zoo-c-log
igi@gentoo ~/test $ ls foo.*
ls: cannot access foo.*: No such file or directory
igi@gentoo ~/test $ ls foo*
foo-a-log  foo-b-log  foo-c-log

.*在正则中匹配了所有,但请记住,shell中绝大多数用的是模式匹配([[ “$string” =~ RE ]]例外)
在模式匹配中*匹配了所有,?匹配了单个字符,.号没有特殊意义,还是.号
所以,第二个命令才是正确

上古键盘的键位

想知道为什么VI和EMACS用的特殊键都是几个比较蹩脚的键位吗? 看看下面这些图:

老的不知道从那里拆的键盘

3ca4e4495c8c973d523bfc7a7afc8a40

现在唯一能找到的市售UNIX键盘HHKB, 下图是HHKB professional 2 标准配列,60键US布局,型号KB400。注意他同时拥有opt,command,compose键。

1024

DEC的终端键盘,真正的串口终端用的键盘:

DEC VT320 keyboard top

经典的SUN type7:

OLYMPUS DIGITAL CAMERA

奇葩的symbolics键盘:

DSC_2077-small keyboard-9647

附加两个老爷计算机的收集站点:

http://www.asl.dsl.pipex.com/symbolics/index.html

http://rcsri.org/collection/index.shtml

关于compose和meta键的设置资料:

https://help.ubuntu.com/community/ComposeKey

http://userbase.kde.org/ComposeKey

http://jp-larocque.livejournal.com/49209.html

http://www.emacswiki.org/emacs/MetaKeyProblems

git 使用ssl代理

发现github中有关youtube的项目都不能pull了,估计是GFW过滤了和youtube关键字有关的任何东西,但办法总是有的

export http_proxy=http://127.0.0.1:XXXX

export https_proxy=https://127.0.0.1:XXXX

GIT_SSL_NO_VERIFY=true git pull

done!!

chrome linux 快捷键

标签页和窗口快捷键

Ctrl+N 打开新窗口。
Ctrl+T 打开新标签页。
Ctrl+Shift+N 在隐身模式下打开新窗口。
按 Ctrl+O,然后选择文件。 在 Google Chrome 浏览器中打开计算机中的文件。
按住 Ctrl 键,然后点击链接。或用鼠标中键(或鼠标滚轮)点击链接。 从后台在新标签页中打开链接。
按住 Ctrl+Shift 键,然后点击链接。或按住 Shift 的同时用鼠标中键(或鼠标滚轮)点击链接。 在新标签页中打开链接并切换到刚打开的标签页。
按住 Shift 键,然后点击链接。 在新窗口中打开链接。
Ctrl+Shift+T 重新打开上次关闭的标签页。Google Chrome 浏览器可记住最近关闭的 10 个标签页。
将链接拖到标签页中。 在标签页中打开链接。
将链接拖到标签栏的空白区域。 在新标签页中打开链接。
将标签页拖出标签栏。 在新窗口中打开标签页。
将标签页从标签栏拖到现有窗口中。 在现有窗口中打开标签页。
拖动标签页的同时按 Esc 键。 将标签页恢复到原先的位置。
Ctrl+1 到 Ctrl+8 切换到标签栏中指定位置编号所对应的标签页。
Ctrl+9 切换到最后一个标签页。
Ctrl+Tab 或 Ctrl+PgDown 切换到下一个标签页。
Ctrl+Shift+Tab或 Ctrl+PgUp 切换到上一个标签页。
Ctrl+Shift+Q 关闭 Google Chrome 浏览器。
Ctrl+W 或 Ctrl+F4 关闭当前标签页或弹出窗口。
用鼠标中键(或鼠标滚轮)点击标签页。 关闭所点击的标签页。
右键点击或者点击并按住浏览器工具栏中的“后退”或“前进”箭头。 在新标签页中显示浏览历史记录。
同时按 Alt 和向左箭头键。 转到当前标签页的上一页浏览历史记录。
同时按 Alt 和向右箭头键。 转到当前标签页的下一页浏览历史记录。
用鼠标中键(或鼠标滚轮)点击工具栏中的后退箭头、前进箭头或转到按钮。 从后台在新标签页中打开按钮所对应的目标网页。
双击标签栏的空白区域。 最大化或最小化窗口。

Google Chrome 浏览器功能快捷键

Alt+F 打开“工具”菜单,用该菜单可自定义和控制 Chrome 浏览器中的设置。
Ctrl+Shift+B 打开和关闭书签栏。
Ctrl+H 打开“历史记录”页面。
Ctrl+J 打开“下载内容”页面。
Shift+Esc 打开任务管理器。
Ctrl+Shift+J 打开开发人员工具。
Ctrl+Shift+Delete 打开“清除浏览数据”对话框。
F1 在新标签页中打开帮助中心(首选方式)。
Ctrl+Shift+M 在多个用户之间切换。

地址栏快捷键

在地址栏中可使用以下快捷键:

输入搜索字词,然后按 Enter 使用默认搜索引擎进行搜索。
输入搜索引擎关键字,按空格键,然后输入搜索字词,再按 Enter 使用与关键字相关联的搜索引擎进行搜索。
首先输入搜索引擎网址,然后在系统提示时按 Tab,输入搜索字词,再按 Enter 使用与网址相关联的搜索引擎进行搜索。
Ctrl+L 突出显示网址。
Ctrl+K 或 Ctrl+E 将“?” 置于地址栏中。在问号后输入搜索字词可用默认搜索引擎执行搜索。
同时按 Ctrl+Shift 和向左箭头键。 将光标移到地址栏中的前一个关键字词
同时按 Ctrl+Shift 和向右箭头键。 在地址栏中将光标移到下一个关键字词
Ctrl+Backspace 在地址栏中删除光标前的关键字词
用键盘上的方向键从地址栏下拉菜单中选择一个条目,然后按 Shift+Delete 从浏览历史记录中删除所选条目(如果可以)。
用鼠标中键(或鼠标滚轮)点击地址栏下拉菜单中的一个条目。 从后台在新标签页中打开该条目。
显示地址栏下拉菜单时按 Page Up 或 Page Down 选中下拉菜单中的第一个或最后一个条目。

网页快捷键

Ctrl+P 打印当前网页。
Ctrl+S 保存当前网页。
Ctrl+R 重新加载当前网页。
Esc 停止载入当前网页。
Ctrl+F 打开查找栏。
Ctrl+G 或 Enter 在查找栏中查找下一条与输入内容相匹配的内容。
Ctrl+Shift+G 或 Shift+Enter 在查找栏中查找上一条与输入内容相匹配的内容。
按住 Alt 键,然后点击链接。 下载链接目标。
Ctrl+U 打开当前网页的源代码。
将链接拖到书签栏中 将链接保存为书签。
Ctrl+D 将当前网页保存为书签。
Ctrl+Shift+D 将所有打开的网页以书签的形式保存在新文件夹中。
F11 在全屏模式下打开网页。再按一次 F11 可退出全屏模式。
同时按 Ctrl 和 +,或按 Ctrl 键并且向上滚动鼠标滚轮。 放大网页上的所有内容。
同时按 Ctrl 和 ,或者按住 Ctrl 并且向下滚动鼠标滚轮。 缩小网页上的所有内容。
Ctrl+0 将网页上的所有内容都恢复到正常大小。
空格键 向下滚动网页。
Home 转至网页顶部。
End 转至网页底部。

文本快捷键

Ctrl+C 将突出显示的内容复制到剪贴板中。
Ctrl+V 或 Shift+Insert 从剪贴板中粘贴内容。
Ctrl+Shift+V 从剪贴板中粘贴内容(不带格式)。
Ctrl+X 或 Shift+Delete 删除突出显示的内容并将其复制到剪贴板中。