awk 学习笔记

awk分别代表其作者姓氏的第一个字母。

awk 基础

awk语法有两种形式:
awk [-F ERE] [-v assignment] 'program' [argument ...]
awk [-F ERE] -f progfile ... [-v assignment] ...[argument ...]

注意:第一种形式的 命令(program) 必须是用单引号引起。 awk -v a='a b c' 'BEGIN {print a; a=123} END{print a}'

标准的awk命令行参数主要由以下三个:

  • -F ERE:定义字段分隔符,该选项的值可以是一个字符串或扩展的正则表达式(ERE),如: -F: 表示以冒号分隔字段。
  • -f progfile:指定awk脚本,可以同时指定多个脚本,它们会按照在命令行中出现的顺序连接在一起。
  • -v assignment:定义awk变量,形式同awk中的变量赋值,即name=value,赋值发生在awk处理文本之前。

awk是一种编程语言,逐行扫描文件,寻找匹配特定模式的行,并在这些行上执行操作。
awk可以同时指定多个输入文件,如果输入文件的文件名为-,表示从标准输入读取内容。

Awk的输入被解析成多个记录(Record),默认情况下,记录的分隔符是 \n,因此可以认为一行就是一个记录,记录的分隔符可以通过内置变量RS更改。当记录匹配某个pattern时,才会执行后续的action命令。

而每个记录又进一步地被分隔成多个字段(Field),默认情况下字段的分隔符是空白符,例如空格、制表符等等,也可以通过-F ERE选项或者内置变量FS更改。在awk中,可以通过 $1,$2… 来访问对应位置的字段,同时 $0 存放整个记录,这一点有点类似shell下的命令行位置参数。

继续阅读

Linux/Unix 设计思想 摘记

良好的程序员写出优秀的软件,优秀的程序员“偷窃”优秀的软件。

NIH(Not Invented Here)综合征的特点就是人们会为了证明自己能够提供更加卓越的解决方案而放弃其他开发人员已经完成的工作。

Unix哲学准则:

  1. 小即是美
  2. 让每一个程序只做好一件事情
  3. 尽快建立原型
  4. 舍高效率而取可移植性
  5. 使用纯文本文件来存储数据
  6. 充分利用软件的杠杆效应
  7. 使用shell脚本来提高杠杆效应和可移植性
  8. 避免强制性的用户界面
  9. 让每一个程序都成为过滤器

Unix哲学的次要准则:

  1. 允许用户定制环境
  2. 尽量使操作系统内核小而轻巧
  3. 使用小写字母,并尽量保持简短
  4. 保护树木
  5. 沉默是金
  6. 并行思考
  7. 各部分之和大于整体
  8. 寻找90%的解决方案
  9. 更坏就是更好
  10. 层次化思考

如果你准备开始编写一个程序,请从小规模开始并尽量保持。

软件开发应该力求简短。

小程序往往只具有单一功能,而单一功能的程序往往也很小。

注:关于小程序和单一功能,我想到以前在IBM DW上看到的一篇文章提出的一种实践规则:
1、一个方法只做一件事;
2、一个方法的代码具有相同的逻辑层次;
3、遵循前面两个规则自然将产生大量短小、具有单一功能的方法,这些方法便于重用。


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

sed 笔记

sed 简介

sed(stream editor)是一个流编辑器,一次处理流的一行内容。

sed 命令模式
sed [options] 'command' file(s)
sed [options] -f scriptfile file(s)

常用options

  • -n 取消默认输出,只有经过sed特殊处理行(或者动作)才会被列出来。
  • -e 多重编辑模式。在命令行中同时指定多个操作指令时才需要用到。
  • -f 指定sed脚本文件名。
  • -i 直接修改文件内容。

sed 不会直接修改原文件,要直接修改原文件有2种方式:
1. 使用 -i 选项;
2. 使用重定向,重定向的目标文件不能与输入文件相同。

sed 的处理流程

每条操作指令由pattern和procedure两部分组成,pattern一般是用 ‘/’ 分隔的正则表达式(在sed中也可能是行号),procedure则是一连串的编辑命令。

sed的处理流程简化为:
1. 读入新的一行内容到缓存空间;
2. 从指定的操作命令中取出第一条指令,判断是否匹配pattern;
3. 如果不匹配,则忽略后续的编辑命令,回到第2步继续取出下一条指令;
4. 如果匹配,则针对缓存的行执行后续的编辑命令;完成后,回到第2步继续取出下一条指令;
5. 当所有指令都应用之后,输出缓存行的内容;回到第1步继续读入下一行内容;
6. 当所有行都处理完成之后,结束。

从流程上可以看出,前一个命令的执行结果影响后一个命令的执行,命令按先后顺序执行。

继续阅读

scp 文件传输

scp

scp是Security copy,基于ssh协议登录,用于在两台主机之间加密拷贝文件或目录,既可以从本机拷贝到远程主机,也可以从远程主机拷贝到本机。


root@coderbee:~# scp
usage: scp [-12346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]
           [-l limit] [-o ssh_option] [-P port] [-S program]
           [[user@]host1:]file1 ... [[user@]host2:]file2

参数说明:

  • -1 强制scp命令使用ssh1协议。
  • -2 强制scp命令使用ssh2协议。
  • -4 强制scp命令只使用IPv4寻址。
  • -6 强制scp命令只使用IPv6寻址。
  • -B 使用批处理模式(传输过程中不询问传输口令或短语)。
  • -C 允许压缩。(将-C标志传给ssh,从而使用压缩传输)
  • -p 保留原文件的修改时间、访问时间和访问权限。
  • -q 不显示传输进度。
  • -r 递归复制整个目录。
  • -v 详细方式显示输出。scp和ssh(1)会显示整个过程的调试信息。用于调试连接、验证和配置问题。
  • -c cipher 以 cipher对数据传输进行加密,这个选项将直接传递给ssh。
  • -F ssh_config 指定一个ssh配置文件,此参数直接传递给ssh。
  • -i identity_file 从指定文件中读取传输时使用的密钥文件,此参数直接传递给ssh。
  • -l limit 限制用户所能使用带宽,以k bit/s为单位。
  • -o ssh_config
  • -P port 大写P,port是传输数据使用的端口号。
  • -S program 指定加密传输时所使用程序,此程序必须能够理解ssh(1)的选项。
  • [[user@]host1:]file1 … 源文件或目录
  • [[user@]host2:]file2 目的文件或目录

选项参数说明来自:http://www.cnblogs.com/xuxm2007/archive/2010/08/04/1792179.html

scp的基本使用形式: scp [-r] 源文件或目录 目的文件或目录-r选项用于拷贝目录。

在本机上的文件或目录是不需要指定用户名和host的。远程主机可以不指定用户名,在命令执行后会要求输入用户名和密码。主机与路径之间是用冒号:分隔的。

对于拷贝遵循:

  • 拷贝文件时,如果目的路径是个目录,则拷贝到目的目录下,文件名不变;如果目的路径是个文件,则拷贝到以此路径指向的文件里,也就是说可以通过指定不同的文件名来直接重命名。
  • 如果是拷贝目录,则目的路径必须是个目录,且必须使用 -r 选项。

举例:

  • 从远程主机拷贝文件到本地目录: scp coderbee@coderbee.net:/home/coderbee/soft/setup.sh .
  • 从本地拷贝文件到远程主机并重命名: scp my.txt coderbee@coderbee.net:/home/coderbee/soft/mytxt
  • 拷贝本地目录到远程主机目录: scp -r basic coderbee@coderbee.net:/home/coderbee/soft/

欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

rsync 核心算法的Java实现

rsync 算法

场景:假设有两台计算机 CA和 CB , CA 上有文件 FA , CB 上有文件 FB , FA 和 FB 是“相似的”。 CA 和 CB 通过低速通信链接连接,现在要把 FA 同步到 FB 上去,如何才能高效同步。

rsync 算法包含下面的步骤:

  1. CB把 FB 分割成固定大小 S 字节的块,最后一块可能少于 S 字节;
  2. 对于每个块,CB 计算两个校验和:一个弱的“滚动” 32 位校验和和一个强的 128 位 MD4 校验和。
  3. CB把这些校验和发给 CA 。
  4. CA搜索 FA 来查找 S 大小的块,这些块与 CB 的块有相同的弱和强校验和。这可以通过一次遍历来完成,通过利用滚动校验和的特殊属性。
  5. CA给 CB 发送一序列指令来构建一个 FA 的拷贝。每个指令要么指向 FB 的一个块,要么是文字数据。文字数据只用于发送 FA 的那些不匹配 FB 块的区域。

最终结果是 CB有了 FA 的拷贝,但只发送了那些在 FB 里找不到的数据。
这个算法只要求一个来回,减少了网络延迟。

这个算法的最重要的细节是滚动校验和 和 associated multi-alternate search mechanism which allows the all-offsets checksum search to proceed very quickly.

这里说到的多选择搜索机制,在实现时用HashMap + List。

继续阅读

Java Socket HTTP

本文最早发表在ITeye上的个人博客http://wen866595.iteye.com/blog/1168658,现在移植到这里,也是一种记忆。

用Java Socket 实现发送HTTP GET请求和读取响应。


public class LearnHttp {
    private static final byte CR = '\r';
    private static final byte LF = '\n';
    private static final byte[] CRLF = {CR, LF};

    public static void main(String[] args) throws UnknownHostException, IOException {
        new LearnHttp().testHttp();
    }
    
    public void testHttp() throws UnknownHostException, IOException {
        String host = "www.baidu.com";
        Socket socket = new Socket(host, 80);
        
        OutputStream out = socket.getOutputStream();
        InputStream in = socket.getInputStream();
        
        // 在同一个TCP连接里发送多个HTTP请求
        for(int i = 0; i < 2; i++) {
            writeRequest(out, host);
            readResponse(in);
            System.out.println("\n\n\n");
        }
    }
    
    private void writeRequest(OutputStream out, String host) throws IOException {
        // 请求行
        out.write("GET /index.html HTTP/1.1".getBytes());
        out.write(CRLF);        // 请求头的每一行都是以CRLF结尾的
        
        // 请求头
        out.write(("Host: " + host).getBytes()); // 此请求头必须
        out.write(CRLF);

        out.write(CRLF);        // 单独的一行CRLF表示请求头的结束

        // 可选的请求体。GET方法没有请求体
        
        out.flush();
    }

    private void readResponse(InputStream in) throws IOException {
        // 读取状态行
        String statusLine = readStatusLine(in);
        System.out.println("statusLine :" + statusLine);
        
        // 消息报头
        Map headers = readHeaders(in);
        
        int contentLength = Integer.valueOf(headers.get("Content-Length"));
        
        // 可选的响应正文
        byte[] body = readResponseBody(in, contentLength);
        
        String charset = headers.get("Content-Type");
        if(charset.matches(".+;charset=.+")) {
            charset = charset.split(";")[1].split("=")[1];
        } else {
            charset = "ISO-8859-1";     // 默认编码
        }
        
        System.out.println("content:\n" + new String(body, charset));
    }
    
    private byte[] readResponseBody(InputStream in, int contentLength) throws IOException {
        
        ByteArrayOutputStream buff = new ByteArrayOutputStream(contentLength);
        
        int b;
        int count = 0;
        while(count++ < contentLength) {
            b = in.read();
            buff.write(b);
        }
        
        return buff.toByteArray();
    }
    
    private Map readHeaders(InputStream in) throws IOException {
        Map headers = new HashMap();
        
        String line;
        
        while(!("".equals(line = readLine(in)))) {
            String[] nv = line.split(": ");     // 头部字段的名值都是以(冒号+空格)分隔的
            headers.put(nv[0], nv[1]);
        }
        
        return headers;
    }
    
    private String readStatusLine(InputStream in) throws IOException {
        return readLine(in);
    }
    
    /**
     * 读取以CRLF分隔的一行,返回结果不包含CRLF
     */
    private String readLine(InputStream in) throws IOException {
        int b;
        
        ByteArrayOutputStream buff = new ByteArrayOutputStream();
        while((b = in.read()) != CR) {
            buff.write(b);
        }
        
        in.read();      // 读取 LF
        
        String line = buff.toString();
        
        return line;
    }
    
}

值得记下的教训:

InputStream.read()返回-1表示流的结束,即流被关闭了。在这次实现同一个Socket发送多个HTTP请求的过程中,一开始总是以为一个响应报文的结束是在InputStream.read返回-1时,导致第一次读取响应时总是很久才结束,第二次的请求虽然发出去了,但响应总是空的。

读取第一次请求的响应要很久是因为,这是在等待服务器关闭连接,百度的基本上是60秒。第二次是空的是因为,请求虽然发出去了,但服务器写响应的流已经被关闭,所以也读不到响应。

导致这个问题的根本原因在于没有记住:TCP是面向流的,并不知道传输的消息的开始和结束,这是由上层的协议去控制的,这里是HTTP协议。

2013-09-01:这是当时尝试HTTP pipeline,由于百度服务器不支持而失败。


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

JNA实例

有时候Java需要通过调用dll来操作底层的硬件或者现有的组件,直接使用JNI是容易出错的,且需要编写本地的C代码。这里介绍另一个调用dll的更简单的方法:JNA。这里介绍JNA调用dll的本地方法、JNA回调Java方法以及JNA相关的一些知识点。如果去留意的话,会发现现在很多jar包都用JNA来调用dll了。

继续阅读

同步方法与同步代码块的区别

当JVM执行一个方法时,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。

同步化一个方法块会超过JVM对获取对象锁和异常处理的内置支持,要求以字节代码显式写入功能。如果使用同步方法读取一个方法的字节代码,就会看到有十几个额外的操作用于管理这个功能。

public class Sync {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

    public int synchronizedBlockGet() {
        synchronized( this ) {
            return i;
        }
    }

}

反编译出的字节码:

D:\Java\jdk1.6.0_02\bin>javap -c Sync
Compiled from "Sync.java"

public class Sync extends java.lang.Object{
public Sync();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public synchronized int synchronizedMethodGet();
  Code:
   0:   aload_0
   1:   getfield        #2; //Field i:I
   4:   ireturn

public int synchronizedBlockGet();
  Code:
   0:   aload_0
   1:   dup
   2:   astore_1
   3:   monitorenter
   4:   aload_0
   5:   getfield        #2; //Field i:I
   8:   aload_1
   9:   monitorexit
   10:  ireturn
   11:  astore_2
   12:  aload_1
   13:  monitorexit
   14:  aload_2
   15:  athrow
  Exception table:
   from   to  target type
     4    10    11   any
    11    14    11   any

}

欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。