Source Insight的强大的代码分析功能让所有windows下的众生受益菲浅。

而Source insight的价格即使是面对Windows Vista也不逞多。嘿嘿。东西是好东西。

个人认为它也对得起这个价格。可惜没米。用不起呀。

咋办呢。用vim,cscope打造一个免费的吧。


1安装cscope

cscope的编译和安装没有特别之处,./configure - make - make install即可。
安转完毕后先阅读说明: vi /usr/share/vim/vim63/doc/if_cscop.txt.gz
网上也有中文版本:http://vcd.gro.clinux.org/doc/if_cscop.html
在vim中使用并不需要进行太多的设置,不过首先vim编译时必须加入了cscope的支持

$ vim --version | grep cscope
+cryptv +cscope +dialog_con +diff +digraphs -dnd -ebcdic +emacs_tags +eval


嗯,我用的这个版本的vim是支持cscope的。

按 照vim里cscope的参考手册(在vim中执行":help cscope"命令),把cscope功能加到.vimrc里后(需要你的vim在编译时选择了"--enable-cscope"选项,否则你需要重新 编译vim),配置就算完成了。然后用下面的命令生成代码的符号索引文件:

    cscope -Rbkq

这个命令会生成三个文件:cscope.out, cscope.in.out, cscope.po.out。
其中cscope.out是基本的符号索引,后两个文件是使用"-q"选项生成的,可以加快cscope的索引速度。
上面所用到的命令参数,含义如下:

-R: 在生成索引文件时,搜索子目录树中的代码
-b: 只生成索引文件,不进入cscope的界面
-k: 在生成索引文件时,不搜索
/usr/include目录
-q: 生成cscope
.in.out和cscope.po.out文件,加快cscope的索引速度


接下来,就可以在vim里读代码了。
不 过在使用过程中,发现无法找到C++的类、函数定义、调用关系。仔细阅读了cscope的手册后发现,原来cscope在产生索引文件时,只搜索类型为 C, lex和yacc的文件(后缀名为.c, .h, .l, .y),C++的文件根本没有生成索引。不过按照手册上的说明,cscope支持c++和Java语言的文件。
于是按照cscope手册上提供的方法,先产生一个文件列表,然后让cscope为这个列表中的每个文件都生成索引。
为了方便使用,编写了下面的脚本来更新cscope和ctags的索引文件:

#!/bin/sh

find . -name "*.h" -o -name "*.c" -o -name "*.cc" > cscope.files
cscope -bkq -i cscope.files
ctags -R


这个脚本,首先使用find命令,查找当前目录及子目录中所有后缀名为".h", ".c"和".cc"的文件,并把查找结果重定向到文件cscope.files中。
然后cscope根据cscope.files中的所有文件,生成符号索引文件。
最后一条命令使用ctags命令,生成一个tags文件,在vim中执行":help tags"命令查询它的用法。它可以和cscope一起使用。

目前只能在unix系列操作系统下使用cscope,虽然也有windows版本的cscope,不过还有很多bug。在Linux技术中坚站上看到有作者在win2000上成功运行了gvim + cscope,详情可以参阅:
http://www.chinalinuxpub.com/bbs/showthread.php?t=30185



cscope的主页在:http://cscope.sourceforge.net/

在vim的网站上,有很多和cscope相关的插件,可以去找一下你有没有所感兴趣的。搜索结果在这里:
点这里


为了方便地使用cscope,我们还需要下载cscope的键盘映射设置,
这样就可以在gvim中简单地通过快捷键来使用 cscope,而不必敲复杂的命令了。键盘映射可以从
这里下载:http://cscope.sourceforge.net/cscope_maps.vim
将下载到的 cscope_maps.vim  
文件: cscope_maps.vim.tar.gz
大小: 2KB
下载: 下载

放在gvim的插件目录里,如 C:\Program Files\Vim\vimfiles\plugin 中。Linux用户可以放在
$HOME/.vim/plugin 中。

建立符号数据库 †
我们假设我们要阅读的代码放在 D:\src\myproject 下。然后打开命令行,进入源代码所在的目录,
为 cscope 建立搜索文件列表。在命令行中执行以下命令:
dir /s /b *.c *.h  > cscope.files
如果你的源代码是C++,则可以将 cpp 等扩展名也加入到上面的命令中。
dir /s /b *.c *.h *cpp *.hpp  > cscope.files
如果是Linux用户,则可以使用 find 命令实现同样的功能:
find $(pwd) -name "*.[ch]"
然后执行以下命令:
cscope -b
执行结束后你可以在当前目录下发现 cscope.out 文件,这就是 cscope 建立的符号数据库。
上面这个命令中,-b参数使得cscope不启动自带的用户界面,而仅仅建立符号数据库。

浏览源代码 †
使用 gvim 打开你的源代码目录中任意一个C程序文件。然后在gvim中执行如下命令:
:cscope add D:\src\myproject\cscope.out
由于在 gvim 中可以使用命令缩写,因此上面的命令可以写成:
:cs a D:\src\myproject\cscope.out
这样就打开了刚刚建立的符号数据库。通过下面的命令可以检查数据库连接的存在。
:cscope show
该命令可以缩写为
:cs s
现在将光标移动到源代码中的某个函数名上,依次按下一下组合键:
s
稍等片刻之后你会在屏幕下放看到如下的字样*1:
Cscope tag: display
   #   line filename / context / line
   1    342 D:\src\myproject\src\global.h <>
             void display(void );
   2    616 D:\src\myproject\src\command.c <>
             display();
   3    138 D:\src\myproject\src\display.c <>
             display(void )
   4    385 D:\src\myproject\src\main.c <>
             display();
   5    652 D:\src\myproject\src\main.c <>
             display();
   6    663 D:\src\myproject\src\main.c <>
             display();
Enter nr or choice ( to abort):
这里显示出的就是整个工程中使用到了 display 这个标识符的位置。此时输入 4,回车,
即可跳转到 main.c 的 385 行调用 display() 函数的地方进行浏览。浏览结束后按 或者
可以回到跳转前的位置。
然后将光标移动到源代码某个函数名上,迅速地依次安下面的组合键:
s
其中 按 Ctrl-2 即可输入。同样,屏幕上出现了一排结果,选择之后你会发现,
跳转到的文件将在水平方向的新窗口中打开。
然后将光标移动到源代码某个函数名上,迅速地依次安下面的组合键:
s
选择之后你会发现,跳转到的文件将在垂直方向的新窗口中打开。
以上我们简单介绍了cscope的使用方法,其中我们只用到了一个 s 命令,即跟在 和 后面的 s 键。
同样,我们可以使用以下的功能键实现不同的跳转功能。
c: 查找该函数被调用的位置 
d: 查找该函数调用了哪些函数
e: 查找指定的正规表达式
f: 查找指定的文件
g: 查找指定标识符的定义位置
i: 查找该文件在哪些地方被包含
s: 查找指定标识符的使用位置
t: 查找指定的文本字符串

命令行使用说明 †
除了上述通过快捷键映射的方式使用cscope之外,也可以直接在gvim命令行中使用cscope。这样就可以
随意定义查找字符串,而不必局限于源代码中已有的标识符。命令格式如下:
:cscope find  <关键字>
该命令可以缩写为
:cs f  <关键字>
一个比较实用的技巧是使用cscope打开文件。使用以下命令即可直接打开名为display.c的文件,
而不必先切换到display.c所在的目录。
:cs f f display.c
cscope也支持正规表达式。如果记不清某个函数的名称,可以用下面的方式来找到该函数的定义位置。
:cs f g .*SetConfiguration.*

版权 †
Cscope虽然不是GPL版权,但是Cscope是开放源码的自由软件,使用Cscope无须支付任何费用。

参考 †
Cscope官方主页, http://cscope.sourceforge.net/
The Vim/Cscope tutorial, http://cscope.sourceforge.net/cscope_vim_tutorial.html
Cscope on Win32, http://iamphet.nm.ru/cscope/
Vim中关于 cscope 的帮助,使用 :help cscope 命令查看
posted @ 2010-08-21 22:28 c++ 学习 阅读(1155) | 评论 (0)编辑 收藏
 

2.用完成例程方式实现的重叠I/O模型
#include <WINSOCK2.H>
#include <stdio.h>

#define PORT    5150
#define MSGSIZE 1024

#pragma comment(lib, "ws2_32.lib")

typedef struct
{
WSAOVERLAPPED overlap;
WSABUF        Buffer;
char          szMessage[MSGSIZE];
DWORD         NumberOfBytesRecvd;
DWORD         Flags; 
SOCKET        sClient;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

DWORD WINAPI WorkerThread(LPVOID);
void CALLBACK CompletionROUTINE(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);

SOCKET g_sNewClientConnection;
BOOL   g_bNewConnectionArrived = FALSE;

int main()
{
WSADATA     wsaData;
SOCKET      sListen;
SOCKADDR_IN local, client;
DWORD       dwThreadId;
int         iaddrSize = sizeof(SOCKADDR_IN);

// Initialize Windows Socket library
WSAStartup(0x0202, &wsaData);

// Create listening socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// Bind
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

// Listen
listen(sListen, 3);

// Create worker thread
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);

while (TRUE)
{
    // Accept a connection
    g_sNewClientConnection = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    g_bNewConnectionArrived = TRUE;
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
}
}

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
LPPER_IO_OPERATION_DATA lpPerIOData = NULL;

while (TRUE)
{
    if (g_bNewConnectionArrived)
    {
      // Launch an asynchronous operation for new arrived connection
      lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
        GetProcessHeap(),
        HEAP_ZERO_MEMORY,
        sizeof(PER_IO_OPERATION_DATA));
      lpPerIOData->Buffer.len = MSGSIZE;
      lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
      lpPerIOData->sClient = g_sNewClientConnection;
      
      WSARecv(lpPerIOData->sClient,
        &lpPerIOData->Buffer,
        1,
        &lpPerIOData->NumberOfBytesRecvd,
        &lpPerIOData->Flags,
        &lpPerIOData->overlap,
        CompletionROUTINE);      
      
      g_bNewConnectionArrived = FALSE;
    }

    SleepEx(1000, TRUE);
}
return 0;
}

void CALLBACK CompletionROUTINE(DWORD dwError,
                                DWORD cbTransferred,
                                LPWSAOVERLAPPED lpOverlapped,
                                DWORD dwFlags)
{
LPPER_IO_OPERATION_DATA lpPerIOData = (LPPER_IO_OPERATION_DATA)lpOverlapped;

if (dwError != 0 || cbTransferred == 0)
{
    // Connection was closed by client
closesocket(lpPerIOData->sClient);
HeapFree(GetProcessHeap(), 0, lpPerIOData);
}
else
{
    lpPerIOData->szMessage[cbTransferred] = '\0';
    send(lpPerIOData->sClient, lpPerIOData->szMessage, cbTransferred, 0);
    
    // Launch another asynchronous operation
    memset(&lpPerIOData->overlap, 0, sizeof(WSAOVERLAPPED));
    lpPerIOData->Buffer.len = MSGSIZE;
    lpPerIOData->Buffer.buf = lpPerIOData->szMessage;   

    WSARecv(lpPerIOData->sClient,
      &lpPerIOData->Buffer,
      1,
      &lpPerIOData->NumberOfBytesRecvd,
      &lpPerIOData->Flags,
      &lpPerIOData->overlap,
      CompletionROUTINE);
}
}

用完成例程来实现重叠I/O比用事件通知简单得多。在这个模型中,主线程只用不停的接受连接即可;辅助线程判断有没有新的客户端连接被建立,如果有,就为那个客户端套接字激活一个异步的WSARecv操作,然后调用SleepEx使线程处于一种可警告的等待状态,以使得I/O完成后CompletionROUTINE可以被内核调用。如果辅助线程不调用SleepEx,则内核在完成一次I/O操作后,无法调用完成例程(因为完成例程的运行应该和当初激活WSARecv异步操作的代码在同一个线程之内)。
完成例程内的实现代码比较简单,它取出接收到的数据,然后将数据原封不动的发送给客户端,最后重新激活另一个WSARecv异步操作。注意,在这里用到了“尾随数据”。我们在调用WSARecv的时候,参数lpOverlapped实际上指向一个比它大得多的结构PER_IO_OPERATION_DATA,这个结构除了WSAOVERLAPPED以外,还被我们附加了缓冲区的结构信息,另外还包括客户端套接字等重要的信息。这样,在完成例程中通过参数lpOverlapped拿到的不仅仅是WSAOVERLAPPED结构,还有后边尾随的包含客户端套接字和接收数据缓冲区等重要信息。这样的C语言技巧在我后面介绍完成端口的时候还会使用到。

五.完成端口模型
“完成端口”模型是迄今为止最为复杂的一种I/O模型。然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!但不幸的是,该模型只适用于Windows NT和Windows 2000操作系统。因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。要记住的一个基本准则是,假如要为Windows NT或Windows 2000开发高性能的服务器应用,同时希望为大量套接字I/O请求提供服务(Web服务器便是这方面的典型例子),那么I/O完成端口模型便是最佳选择!(节选自《Windows网络编程》第八章)
完成端口模型是我最喜爱的一种模型。虽然其实现比较复杂(其实我觉得它的实现比用事件通知实现的重叠I/O简单多了),但其效率是惊人的。我在T公司的时候曾经帮同事写过一个邮件服务器的性能测试程序,用的就是完成端口模型。结果表明,完成端口模型在多连接(成千上万)的情况下,仅仅依靠一两个辅助线程,就可以达到非常高的吞吐量。下面我还是从代码说起:
#include <WINSOCK2.H>
#include <stdio.h>

#define PORT    5150
#define MSGSIZE 1024

#pragma comment(lib, "ws2_32.lib")

typedef enum
{
RECV_POSTED
}OPERATION_TYPE;

typedef struct
{
WSAOVERLAPPED overlap;
WSABUF         Buffer;
char           szMessage[MSGSIZE];
DWORD          NumberOfBytesRecvd;
DWORD          Flags;
OPERATION_TYPE OperationType;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

DWORD WINAPI WorkerThread(LPVOID);

int main()
{
WSADATA                 wsaData;
SOCKET                  sListen, sClient;
SOCKADDR_IN             local, client;
DWORD                   i, dwThreadId;
int                     iaddrSize = sizeof(SOCKADDR_IN);
HANDLE                  CompletionPort = INVALID_HANDLE_VALUE;
SYSTEM_INFO             systeminfo;
LPPER_IO_OPERATION_DATA lpPerIOData = NULL;

// Initialize Windows Socket library
WSAStartup(0x0202, &wsaData);

// Create completion port
CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

// Create worker thread
GetSystemInfo(&systeminfo);
for (i = 0; i < systeminfo.dwNumberOfProcessors; i++)
{
    CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &dwThreadId);
}

// Create listening socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// Bind
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

// Listen
listen(sListen, 3);

while (TRUE)
{
    // Accept a connection
    sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

    // Associate the newly arrived client socket with completion port
    CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)sClient, 0);
    
    // Launch an asynchronous operation for new arrived connection
    lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
      GetProcessHeap(),
      HEAP_ZERO_MEMORY,
      sizeof(PER_IO_OPERATION_DATA));
    lpPerIOData->Buffer.len = MSGSIZE;
    lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
    lpPerIOData->OperationType = RECV_POSTED;
    WSARecv(sClient,
      &lpPerIOData->Buffer,
      1,
      &lpPerIOData->NumberOfBytesRecvd,
      &lpPerIOData->Flags,
      &lpPerIOData->overlap,
      NULL);
}

PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL);
CloseHandle(CompletionPort);
closesocket(sListen);
WSACleanup();
return 0;
}

DWORD WINAPI WorkerThread(LPVOID CompletionPortID)
{
HANDLE                  CompletionPort=(HANDLE)CompletionPortID;
DWORD                   dwBytesTransferred;
SOCKET                  sClient;
LPPER_IO_OPERATION_DATA lpPerIOData = NULL;

while (TRUE)
{
    GetQueuedCompletionStatus(
      CompletionPort,
      &dwBytesTransferred,
      &sClient,
      (LPOVERLAPPED *)&lpPerIOData,
      INFINITE);
    if (dwBytesTransferred == 0xFFFFFFFF)
    {
      return 0;
    }
    
    if (lpPerIOData->OperationType == RECV_POSTED)
    {
      if (dwBytesTransferred == 0)
      {
        // Connection was closed by client
        closesocket(sClient);
        HeapFree(GetProcessHeap(), 0, lpPerIOData);        
      }
      else
      {
        lpPerIOData->szMessage[dwBytesTransferred] = '\0';
        send(sClient, lpPerIOData->szMessage, dwBytesTransferred, 0);
        
        // Launch another asynchronous operation for sClient
        memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));
        lpPerIOData->Buffer.len = MSGSIZE;
        lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
        lpPerIOData->OperationType = RECV_POSTED;
        WSARecv(sClient,
          &lpPerIOData->Buffer,
          1,
          &lpPerIOData->NumberOfBytesRecvd,
          &lpPerIOData->Flags,
          &lpPerIOData->overlap,
          NULL);
      }
    }
}
return 0;
}


首先,说说主线程:
1.创建完成端口对象
2.创建工作者线程(这里工作者线程的数量是按照CPU的个数来决定的,这样可以达到最佳性能)
3.创建监听套接字,绑定,监听,然后程序进入循环
4.在循环中,我做了以下几件事情:
(1).接受一个客户端连接
(2).将该客户端套接字与完成端口绑定到一起(还是调用CreateIoCompletionPort,但这次的作用不同),注意,按道理来讲,此时传递给CreateIoCompletionPort的第三个参数应该是一个完成键,一般来讲,程序都是传递一个单句柄数据结构的地址,该单句柄数据包含了和该客户端连接有关的信息,由于我们只关心套接字句柄,所以直接将套接字句柄作为完成键传递;
(3).触发一个WSARecv异步调用,这次又用到了“尾随数据”,使接收数据所用的缓冲区紧跟在WSAOVERLAPPED对象之后,此外,还有操作类型等重要信息。

在工作者线程的循环中,我们
1.调用GetQueuedCompletionStatus取得本次I/O的相关信息(例如套接字句柄、传送的字节数、单I/O数据结构的地址等等)
2.通过单I/O数据结构找到接收数据缓冲区,然后将数据原封不动的发送到客户端
3.再次触发一个WSARecv异步操作

六.五种I/O模型的比较
我会从以下几个方面来进行比较
*有无每线程64连接数限制
如果在选择模型中没有重新定义FD_SETSIZE宏,则每个fd_set默认可以装下64个SOCKET。同样的,受MAXIMUM_WAIT_OBJECTS宏的影响,事件选择、用事件通知实现的重叠I/O都有每线程最大64连接数限制。如果连接数成千上万,则必须对客户端套接字进行分组,这样,势必增加程序的复杂度。
相反,异步选择、用完成例程实现的重叠I/O和完成端口不受此限制。

*线程数
除了异步选择以外,其他模型至少需要2个线程。一个主线程和一个辅助线程。同样的,如果连接数大于64,则选择模型、事件选择和用事件通知实现的重叠I/O的线程数还要增加。

*实现的复杂度
我的个人看法是,在实现难度上,异步选择<选择<用完成例程实现的重叠I/O<事件选择<完成端口<用事件通知实现的重叠I/O

*性能
由于选择模型中每次都要重设读集,在select函数返回后还要针对所有套接字进行逐一测试,我的感觉是效率比较差;完成端口和用完成例程实现的重叠I/O基本上不涉及全局数据,效率应该是最高的,而且在多处理器情形下完成端口还要高一些;事件选择和用事件通知实现的重叠I/O在实现机制上都是采用WSAWaitForMultipleEvents,感觉效率差不多;至于异步选择,不好比较。所以我的结论是:选择<用事件通知实现的重叠I/O<事件选择<用完成例程实现的重叠I/O<完成端口

posted @ 2010-08-03 17:27 c++ 学习 阅读(419) | 评论 (0)编辑 收藏
 
     摘要: 如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。Windows操作系统提供了选择(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O)和完成端口(Completion Port)共五种I/O模型。每一种模型均适用于一种特定的应用场景。程序员应该对自己的应用需求非常明确,而且综合考虑...  阅读全文
posted @ 2010-08-03 17:26 c++ 学习 阅读(443) | 评论 (0)编辑 收藏
 
     摘要: 本文简单介绍了当前Windows支持的各种Socket I/O模型,如果你发现其中存在什么错误请务必赐教。一:select模型二:WSAAsyncSelect模型三:WSAEventSelect模型四:Overlapped I/O 事件通知模型五:Overlapped I/O 完成例程模型六:IOCP模型老陈有一个在外地工作的女儿,不能经常回来,老陈和她通过信件联系。他们的信会被邮递员投递到他们的...  阅读全文
posted @ 2010-08-03 15:50 c++ 学习 阅读(333) | 评论 (0)编辑 收藏
 
这种使用间接引用的方法是一个小技巧. 如果第二个变量更改了它的值, 那么第一个变量
必须被适当的解除引用(就像上边的例子一样). 幸运的是, 在Bash版本2中引入
的${!variable}形式使得使用间接引用更加直观了.
假设一个变量的值是第二个变量的名字. 那么我们如何从第一个变量中取得第二个变量的值呢? 比如,
如果a=letter_of_alphabet并且letter_of_alphabet=z, 那么我们能够通过引用变量a来获得z么? 这确
实是可以做到的, 它被称为间接引用. 它使用eval var1=\$$var2这种不平常的形式.
posted @ 2010-08-02 16:46 c++ 学习 阅读(1335) | 评论 (0)编辑 收藏
 

正则表达式就是由一系列特殊字符组成的字符串, 其中每个特殊字符都被称为元字符, 这些元字符并不表示为它们字面上的含义, 而会被解释为一些特定的含义. 具个例子, 比如引用符号, 可能就是表示某人的演讲内容, 同上, 也可能表示为我们下面将要讲到的符号的元-含义. 正则表达式其实是由普通字符和元字符共同组成的集合, 这个集合用来匹配(或指定)模式.

一个正则表达式会包含下列一项或多项:

  • 一个字符集. 这里所指的字符集只包含普通字符, 这些字符只表示它们的字面含义. 正则表达式的最简单形式就是包含字符集, 而不包含元字符.

  • . 指定了正则表达式所要匹配的文本在文本行中所处的位置. 比如, ^, 和$就是锚.

  • 修饰符. 它们扩大或缩小(修改)了正则表达式匹配文本的范围. 修饰符包含星号, 括号, 和反斜杠.

正则表达式最主要的目的就是用于(RE)文本搜索与字符串操作. (译者注: 以下正则表达式也会被简称为RE.) RE能够匹配单个字符或者一个字符集 -- 即, 一个字符串, 或者一个字符串的一部分.

  • 星号 -- * -- 用来匹配它前面字符的任意多次, 包括0次.

    "1133*"匹配11 + 一个或多个3 + 也允许后边还有其他字符: 113, 1133, 111312, 等等.

  • 点 -- . -- 用于匹配任意一个字符, 除了换行符. [1]

    "13." 匹配13 + 至少一个任意字符(包括空格): 1133, 11333, 但不能匹配13 (因为缺少"."所能匹配的至少一个任意字符).

  • 脱字符号 -- ^ -- 匹配行首, 但是某些时候需要依赖上下文环境, 在RE中, 有时候也表示对一个字符集取反.

  • 美元符 -- $ -- 在RE中用来匹配行尾.

    "XXX$" 匹配行尾的XXX.

    "^$" 匹配空行.

  • 中括号 -- [...] -- 在RE中, 将匹配中括号字符集中的某一个字符.

    "[xyz]" 将会匹配字符x, y, 或z.

    "[c-n]" 匹配字符c到字符n之间的任意一个字符.

    "[B-Pk-y]" 匹配从BP, 或者从ky之间的任意一个字符.

    "[a-z0-9]" 匹配任意小写字母或数字.

    "[^b-d]" 将会匹配范围在bd之外的任意一个字符. 这就是使用^对字符集取反的一个实例. (就好像在某些情况下, !所表达的含义).

    将多个中括号字符集组合使用, 能够匹配一般的单词或数字. "[Yy][Ee][Ss]"能够匹配yes, Yes, YES, yEs, 等等. "[0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9]" 可以匹配社保码(Social Security number).

  • 反斜杠 -- \ -- 用来转义某个特殊含义的字符, 这意味着, 这个特殊字符将会被解释为字面含义.

    "\$"将会被解释成字符"$", 而不是RE中匹配行尾的特殊字符. 相似的, "\\"将会被解释为字符"\".

  • 转义"尖括号" -- \<...\> -- 用于匹配单词边界.

    尖括号必须被转义才含有特殊的含义, 否则它就表示尖括号的字面含义.

    "\<the\>" 完整匹配单词"the", 不会匹配"them", "there", "other", 等等.

     

    bash$ cat textfile
    This is line 1, of which there is only one instance.
    This is the only instance of line 2.
    This is line 3, another line.
    This is line 4.



    bash$ grep 'the' textfile
    This is line 1, of which there is only one instance.
    This is the only instance of line 2.
    This is line 3, another line.



    bash$ grep '\<the\>' textfile
    This is the only instance of line 2.
  • 扩展的正则表达式. 添加了一些额外的匹配字符到基本集合中. 用于egrep, awk, 和Perl.

  • 问号 -- ? -- 匹配它前面的字符, 但是只能匹配1次或0次. 通常用来匹配单个字符.

  • 加号 -- + -- 匹配它前面的字符, 能够匹配一次或多次. 与前面讲的*号作用类似, 但是不能匹配0个字符的情况.

      1 # GNU版本的sed和awk能够使用"+",
    2 # 但是它需要被转义一下.

    4 echo a111b | sed -ne '/a1\+b/p'
    5 echo a111b | grep 'a1\+b'
    6 echo a111b | gawk '/a1+b/'
    7 # 上边3句的作用相同.

    9 # 感谢, S.C.
  • 转义"大括号" -- \{ \} -- 在转义后的大括号中加上一个数字, 这个数字就是它前面的RE所能匹配的次数.

    大括号必须经过转义, 否则, 大括号仅仅表示字面含意. 这种用法并不是基本RE集合中的一部分, 仅仅是个技巧而以.

    "[0-9]\{5\}" 精确匹配5个数字 (所匹配的字符范围是0到9).

    Note

    使用大括号形式的RE是不能够在"经典"(非POSIX兼容)的awk版本中正常运行的. 然而, gawk命令中有一个--re-interval选项, 使用这个选项就允许使用大括号形式的RE了(无需转义).

     

    bash$ echo 2222 | gawk --re-interval '/2{3}/'
    2222

    Perl与某些版本的egrep不需要转义大括号.

  • 圆括号 -- ( ) -- 括起一组正则表达式. 当你想使用expr进行子字符串提取(substring extraction)的时候, 圆括号就有用了. 如果和下面要讲的"|"操作符结合使用, 也非常有用.

  • 竖线 -- | -- 就是RE中的"或"操作符, 使用它能够匹配一组可选字符中的任意一个.

     

    bash$ egrep 're(a|e)d' misc.txt
    People who read seem to be better informed than those who do not.
    The clarinet produces sound by the vibration of its reed.

Note

与GNU工具一样, 某些版本的sed, ed, 和ex一样能够支持扩展正则表达式, 上边这部分就描述了扩展正则表达式.

  • POSIX字符类. [:class:]

    这是另外一种, 用于指定匹配字符范围的方法.

  • [:alnum:] 匹配字母和数字. 等价于A-Za-z0-9.

  • [:alpha:] 匹配字母. 等价于A-Za-z.

  • [:blank:] 匹配一个空格或是一个制表符(tab).

  • [:cntrl:] 匹配控制字符.

  • [:digit:] 匹配(十进制)数字. 等价于0-9.

  • [:graph:] (可打印的图形字符). 匹配ASCII码值范围在33 - 126之间的字符. 与下面所提到的[:print:]类似, 但是不包括空格字符(空格字符的ASCII码是32).

  • [:lower:] 匹配小写字母. 等价于a-z.

  • [:print:] (可打印的图形字符). 匹配ASCII码值范围在32 - 126之间的字符. 与上边的[:graph:]类似, 但是包含空格.

  • [:space:] 匹配空白字符(空格和水平制表符).

  • [:upper:] 匹配大写字母. 等价于A-Z.

  • [:xdigit:] 匹配16进制数字. 等价于0-9A-Fa-f.

    Important

    POSIX字符类通常都要用引号或双中括号([[ ]])引起来.

     

    bash$ grep [[:digit:]] test.file
    abc=723

    如果在一个受限的范围内, 这些字符类甚至可以用在通配(globbing)中.

     

    bash$ ls -l ?[[:digit:]][[:digit:]]?
    -rw-rw-r-- 1 bozo bozo 0 Aug 21 14:47 a33b

    如果想了解POSIX字符类在脚本中的使用情况, 请参考例子 12-18例子 12-19.

Sed, awk, 和Perl在脚本中一般都被用作过滤器, 这些过滤器将会以RE为参数, 对文件或者I/O流进行"过滤"或转换. 请参考例子 A-12例子 A-17, 来详细了解这种用法.

对于RE这个复杂的主题, 标准的参考材料是Friedl的Mastering Regular Expressions. 由Dougherty和Robbins所编写的Sed & Awk这本书, 也对RE进行了清晰的论述. 如果想获得这些书的更多信息, 请察看参考文献.

注意事项

[1]

因为sed, awk, 和grep通常用于处理单行, 但是不能匹配一个换行符. 如果你想处理多行输入的话, 那么你可以使用"点"来匹配换行符.

  1 #!/bin/bash

3 sed -e 'N;s/.*/[&]/' << EOF # Here Document
4 line1
5 line2
6 EOF
7 # 输出:
8 # [line1
9 # line2]
10 
11 
12 
13 echo
14 
15 awk '{ $0=$1 "\n" $2; if (/line.1/) {print}}' << EOF
16 line 1
17 line 2
18 EOF
19 # 输出:
20 # line
21 # 1
22 
23 
24 # 感谢, S.C.
25
26 exit 0

 

posted @ 2010-06-30 22:15 c++ 学习 阅读(7750) | 评论 (1)编辑 收藏
 

1.       expr expression

expr只能用于一元操作符,不支持二元操作符

1 x=1

2 x=$(expr $x + 1)

$x + 1之间必须有空格

2.       let expression

let 的使用方式

x=10

let x=$x+1

let x+=1

let x*=10

Let没有返回值

3.       使用$((expression ))((expression))形式

((expression))的使用方法

x=10

((x+=10))

(( expression)) 用法和let类似

$(())的使用用法

$((x=$x+10))

echo $x

y=$((x=$x-10))

echo $y

y=$(($x+1))

echo $y

echo $x

 

4.       使用$[  ]形式

例如:

n=1

:  $[ n=$n+1 ](:和$之间有空格)

y=$[ n = $n + 1 ]

echo $y

y=$[ $n+1 ]

echo $y

 

5.       使用decalare 

例子:

decare –i num

num=$num+1

echo $num

posted @ 2010-06-28 17:03 c++ 学习 阅读(2731) | 评论 (0)编辑 收藏
 

Bash Process Substitution

In addition to the fairly common forms of input/output redirection the shell recognizes something called process substitution. Although not documented as a form of input/output redirection, its syntax and its effects are similar.

The syntax for process substitution is:

  <(list)
or
  >(list)
where each list is a command or a pipeline of commands. The effect of process substitution is to make each list act like a file. This is done by giving the list a name in the file system and then substituting that name in the command line. The list is given a name either by connecting the list to named pipe or by using a file in /dev/fd (if supported by the O/S). By doing this, the command simply sees a file name and is unaware that its reading from or writing to a command pipeline.

 

To substitute a command pipeline for an input file the syntax is:

  command ... <(list) ...
To substitute a command pipeline for an output file the syntax is:
  command ... >(list) ...

 

At first process substitution may seem rather pointless, for example you might imagine something simple like:

  uniq <(sort a)
to sort a file and then find the unique lines in it, but this is more commonly (and more conveniently) written as:
  sort a | uniq
The power of process substitution comes when you have multiple command pipelines that you want to connect to a single command.

 

For example, given the two files:

  # cat a
  e
  d
  c
  b
  a
  # cat b
  g
  f
  e
  d
  c
  b
To view the lines unique to each of these two unsorted files you might do something like this:
  # sort a | uniq >tmp1
  # sort b | uniq >tmp2
  # comm -3 tmp1 tmp2
  a
        f
        g
  # rm tmp1 tmp2
With process substitution we can do all this with one line:
  # comm -3 <(sort a | uniq) <(sort b | uniq)
  a
        f
        g

 

Depending on your shell settings you may get an error message similar to:

  syntax error near unexpected token `('
when you try to use process substitution, particularly if you try to use it within a shell script. Process substitution is not a POSIX compliant feature and so it may have to be enabled via:
  set +o posix
Be careful not to try something like:
  if [[ $use_process_substitution -eq 1 ]]; then
    set +o posix
    comm -3 <(sort a | uniq) <(sort b | uniq)
  fi
The command set +o posix enables not only the execution of process substitution but the recognition of the syntax. So, in the example above the shell tries to parse the process substitution syntax before the "set" command is executed and therefore still sees the process substitution syntax as illegal.

 

Of course, note that all shells may not support process substitution, these examples will work with bash.


进程替换与命令替换很相似. 命令替换把一个命令的结果赋值给一个变量, 比如dir_contents=`ls -

al`或xref=$( grep word datafile). 进程替换把一个进程的输出提供给另一个进程(换句话说, 它把

一个命令的结果发给了另一个命令).

命令替换的模版

用圆括号扩起来的命令

>(command)

<(command)

启动进程替换. 它使用/dev/fd/<n>文件将圆括号中的进程处理结果发送给另一个进程. [1] (译

者注: 实际上现代的UNIX类操作系统提供的/dev/fd/n文件是与文件描述符相关的, 整数n指的就

是进程运行时对应数字的文件描述符)

在"<"或">"与圆括号之间是没有空格的. 如果加了空格, 会产生错误.

bash$ echo >(true)

/dev/fd/63

bash$ echo <(true)

/dev/fd/63

Bash在两个文件描述符之间创建了一个管道, --fIn和fOut--. true命令的stdin被连接到fOut

(dup2(fOut, 0)), 然后Bash把/dev/fd/fIn作为参数传给echo. 如果系统缺乏/dev/fd/<n>文件, Bash会

使用临时文件. (感谢, S.C.)

进程替换可以比较两个不同命令的输出, 甚至能够比较同一个命令不同选项情况下的输出.

bash$ comm <(ls -l) <(ls -al)

total 12

-rw-rw-r-- 1 bozo bozo 78 Mar 10 12:58 File0

-rw-rw-r-- 1 bozo bozo 42 Mar 10 12:58 File2

-rw-rw-r-- 1 bozo bozo 103 Mar 10 12:58 t2.sh

total 20

drwxrwxrwx 2 bozo bozo 4096 Mar 10 18:10 .

drwx------ 72 bozo bozo 4096 Mar 10 17:58 ..

-rw-rw-r-- 1 bozo bozo 78 Mar 10 12:58 File0

-rw-rw-r-- 1 bozo bozo 42 Mar 10 12:58 File2

-rw-rw-r-- 1 bozo bozo 103 Mar 10 12:58 t2.sh

使用进程替换来比较两个不同目录的内容(可以查看哪些文件名相同, 哪些文件名不同):

1 diff <(ls $first_directory) <(ls $second_directory)

一些进程替换的其他用法与技巧:

1 cat <(ls -l)

2 # 等价于 ls -l | cat

3

4 sort -k 9 <(ls -l /bin) <(ls -l /usr/bin) <(ls -l /usr/X11R6/bin)

5 # 列出系统3个主要'bin'目录中的所有文件, 并且按文件名进行排序.

6 # 注意是3个(查一下, 上面就3个圆括号)明显不同的命令输出传递给'sort'.

7

8

9 diff <(command1) <(command2) # 给出两个命令输出的不同之处.

10

11 tar cf >(bzip2 -c > file.tar.bz2) $directory_name

12 # 调用"tar cf /dev/fd/?? $directory_name", 和"bzip2 -c > file.tar.bz2".

13 #

14 # 因为/dev/fd/<n>的系统属性,

15 # 所以两个命令之间的管道不必被命名.

16 #

17 # 这种效果可以被模拟出来.

18 #

19 bzip2 -c < pipe > file.tar.bz2&

20 tar cf pipe $directory_name

21 rm pipe

22 # 或

23 exec 3>&1

24 tar cf /dev/fd/4 $directory_name 4>&1 >&3 3>&- | bzip2 -c > file.tar.bz2 3>&-

25 exec 3>&-

26

27

28 # 感谢, Stephane Chazelas

一个读者给我发了一个有趣的例子, 是关于进程替换的, 如下.

1 # 摘自SuSE发行版中的代码片断:

2

3 while read des what mask iface; do

4 # 这里省略了一些命令...

5 done < <(route -n)

6

7

8 # 为了测试它, 我们让它做点事.

9 while read des what mask iface; do

10 echo $des $what $mask $iface

11 done < <(route -n)

12

13 # 输出:

14 # Kernel IP routing table

15 # Destination Gateway Genmask Flags Metric Ref Use Iface

16 # 127.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 lo

17

18

19

20 # 就像Stephane Chazelas所给出的那样, 一个更容易理解的等价代码是:

21 route -n |

22 while read des what mask iface; do # 管道的输出被赋值给了变量.

23 echo $des $what $mask $iface

24 done # 这将产生出与上边相同的输出.

25 # 然而, Ulrich Gayer指出 . . .

26 #+ 这个简单的等价版本在while循环中使用了一个子shell,

27 #+ 因此当管道结束后, 变量就消失了.

28

29

30

31 # 更进一步, Filip Moritz解释了上面两个例子之间存在一个细微的不同之处,

32 #+ 如下所示.

33

34 (

35 route -n | while read x; do ((y++)); done

36 echo $y # $y 仍然没有被声明或设置

37

38 while read x; do ((y++)); done < <(route -n)

39 echo $y # $y 的值为route -n的输出行数.

40 )

41

42 # 一般来说, (译者注: 原书作者在这里并未加注释符号"#", 应该是笔误)

43 (

44 : | x=x

45 # 看上去是启动了一个子shell

46 : | ( x=x )

47 # 但

48 x=x < <(:)

49 # 其实不是

50 )

51

52 # 当你要解析csv或类似东西的时侯, 这非常有用.

53 # 事实上, 这就是SuSE的这个代码片断所要实现的功能.

注意事项

[1] 这与命名管道(临时文件)具有相同的作用, 并且, 事实上, 命名管道也被同时使用在进程

替换中.

posted @ 2010-06-27 17:15 c++ 学习 阅读(1292) | 评论 (0)编辑 收藏
 
几个知识点
1.Bash在实现pipeline(管道|)时会发起两个subshell(子shell)来运行|两边的命令,对于系统来说就是发起两个childprocess(子进程)

2.fork是产生process的唯一途径,exec*是执行程序的唯一途径

3.子进程会完全复制父进程,除了$PID与$PPID

4.fork子进程时继承父进程的进程名,在exec*执行命令时才由exec*替换为子进程对应的命令,同一进程的命令名可以由一个个exec*任意多次的改变



[注]对于linux平台,JB上就是这样的,其它平台不好发表意见,当然对于2中的两个唯一有一个例外,就是在kenerl  init的初期;
暂时找不到相关参考,也没有功力读源码,所以此论是道听途说级别,错误之处请指出改正,如果没有改正的价值可一笑而过

我觉得要先弄清楚sub shell的定义。

查了些资料,发现subshell的定义有些混乱。
bashMan:



QUOTE:
Each command in a pipeline is executed as a separate process (i.e., in a subshell).  




QUOTE:
When a simple command other than a builtin or shell function is to be executed, it is invoked in a
separate execution environment that consists of the following. Unless otherwise noted, the values are
inherited from the shell.



这个separate execution是subshell吗?
A. 在当前shell执行外部命令,如shell> date, 是fork+exec, 算不是subshell? 
B. ()是fork一个child shell,该child再fork+exec来执行命令。这个subshell和A中的"subshell"显然是不同的。

UNIX: The Textbook, by Syed Mansoor Sarvar, Robert Koretsky and Syed Aqeel Sarvar中提到:


QUOTE:
A child shell is also called subshell


问题是fork+exec是fork一个child shell,然后在该child shell中exec.
而执行脚本(shell>scriptname)时,是fort一个child shell A,该child shell A再fork一个child shell B, 在B中再exec.

那么child shell是指哪种情况?



UNIX at Fermilab中的Important UNIX Concepts:



QUOTE:
When you give the shell a command associated with a compiled executable or shell script, the shell
creates, or forks, a new process called a subshell.


外部命令也在subshell中执行。



QUOTE:
To execute most built-in commands, the shell forks a subshell which executes the command directly (no
exec system call). For the built-in commands cd, set, alias and source, the current shell executes the
command; no subshell is forked.



shell> built-inCommands这样执行时,大部分内部命令也是在subshell中执行。
可见,UNIX at Fermilab认为fork 一个child shell就是subshell, 不管是fork-fork+exec, 还是 fork+exec。

我刚才又去翻了下ABS,定义与我的理解不一样


QUOTE:
A subshell is a separate instance of the command processor -- the shell that gives you the prompt at the
console or in an xterm window. Just as your commands are interpreted at the command line prompt, similarly
does a script batch-process a list of commands. Each shell script running is, in effect, a subprocess (child
process) of the parent shell.
./script也是external_cmd的一种啊,都是fork-exec,至于external-cmd再作什么动作完全是external-cmd的

posted @ 2010-06-25 16:35 c++ 学习 阅读(801) | 评论 (0)编辑 收藏
 
good job!
總算有人看得懂了。

不過,要細說的話,要扯上 shell 在 interpret 一個 command line 時的 priority 。
基本上,其順序如下:
1,將 line 拆成 words (IFS很重要)
2,括展 alias
3,擴展 { }
4,擴展 ~
5,擴展 $variable, $(command), `command`
6,重組再拆成 words
7,括展 wildcards
8,處理 I/O redirection
9,載入命令運行
如果大家有O'Reilly英文版的 Learning the Bash(2nd)的話,請多端詳p178的圖(細節略異)

回到LZ的問題,看上面 5 跟 6 的順序然後才是 9 。
也就是在 6 重組命令時 $A 已經完成替換,當時的 environment 是沒賦值,
因此重組後就是 A=B echo
然後在第 9 的步驟運行命令時, A=B 是給 echo 命令的 local environment,
不管是否 built-in command,都不影響當前的 shell (不同的 shell 在實作上或有差異)
所以第二行的 echo $A 也是得到沒賦值
我通过eval说明赋值是成功的,而不是65楼所说的赋值不成功。

第一步使用 metacharacter,与IFS没有关系

The following is a brief description of the shell's operation when it
reads and executes a command.  Basically, the shell does the following:

  1. Reads its input from a file (*note Shell Scripts::), from a string
     supplied as an argument to the `-c' invocation option (*note
     Invoking Bash::), or from the user's terminal.

  2. Breaks the input into words and operators, obeying the quoting
     rules described in *Note Quoting::.  These tokens are separated by
     `metacharacters'.  Alias expansion is performed by this step
     (*note Aliases::).
QUOTE:
`IFS'
     A list of characters that separate fields; used when the shell
     splits words as part of expansion.
`metacharacter'
     A character that, when unquoted, separates words.  A metacharacter
     is a `blank' or one of the following characters: `|', `&', `;',
     `(', `)', `<', or `>'.
8.05 命令行的评价(evaluation)
下面是C shell 解释命令行的顺序:
1. 历史替换
2. 分裂词(包括特殊字符)
3. 更新历史表
4. 解释单引号(') 和 双引号(")
5. 别名替换
6. 输入和输出的重定向(如 >  < 和 |)
7. 变量替换
8. 命令替换
9. 文件名扩展
(Bourne shell 的解释顺序本质上是一样的,除了它不执行历史替换和别名替换之外)

所以
A=B  echo    $A

的执行过程应该是这样的:
1. 没有历史操作符, 因此不进行历史替换(Bourne shell 不执行这一步)
2. 分裂词,每碰到未加引号的空白字符就会产生一个新“词”。这些词是 A=B、echo、$A。
3. shell 将命令行放到历史列表中。(Bourne shell 不执行这一步)
4. 没有引号需要解释
5. 没有别名需要替换
6. 没有输入或输出重定向需要处理
7. shell注意到变量$A,并把它替换成空
8. shell寻找左单引号,执行左单引号中的任何命令,并且将命令的输出插入到命令行中。在本例中,没有这方面的事需要做。(如果左单引号内有通配符或者变量,那么在shell运行左单引号中的命令之前它们是不会被解释的)
9. shell寻找通配符。本例中没有,不需要处理
10. shell 执行 A=B, 执行 echo 。
8.05 命令行的评价(evaluation)
下面是C shell 解释命令行的顺序:
1. 历史替换
2. 分裂词(包括特殊字符)
3. 更新历史表
4. 解释单引号(') 和 双引号(")
5. 别名替换
6. 输入和输出的重定向(如 >  < 和 |)
7. 变量替换
8. 命令替换
9. 文件名扩展
(Bourne shell 的解释顺序本质上是一样的,除了它不执行历史替换和别名替换之外)

所以
A=B  echo    $A

的执行过程应该是这样的:
1. 没有历史操作符, 因此不进行历史替换(Bourne shell 不执行这一步)
2. 分裂词,每碰到未加引号的空白字符就会产生一个新“词”。这些词是 A=B、echo、$A。
3. shell 将命令行放到历史列表中。(Bourne shell 不执行这一步)
4. 没有引号需要解释
5. 没有别名需要替换
6. 没有输入或输出重定向需要处理
7. shell注意到变量$A,并把它替换成空
8. shell寻找左单引号,执行左单引号中的任何命令,并且将命令的输出插入到命令行中。在本例中,没有这方面的事需要做。(如果左单引号内有通配符或者变量,那么在shell运行左单引号中的命令之前它们是不会被解释的)
9. shell寻找通配符。本例中没有,不需要处理
10. shell 执行 A=B, 执行 echo 。

posted @ 2010-06-25 16:29 c++ 学习 阅读(513) | 评论 (0)编辑 收藏
仅列出标题
共3页: 1 2 3