zsh-字符串转义和格式化

导读

上一篇讲了 zsh 的常用字符串操作,这篇开始讲更为琐碎的转义字符和格式化输出相关内容。包括转义字符、引号、printprintf 的使用等等。其中很多内容没有必要记忆,作为手册参考即可。

转义字符

转义字符是很多编程语言中都有的概念,它主要解决某些字符因为没有对应键盘按键无法直接输出、字符本身有特殊含义(比如 \")或者显示不直观(比如难以区别多个空格和一个 tab)等问题。

最常用的转义字符是 \n(换行)、\r(回车)、\t(tab)。

直接用 echoprint 或者 printf 内置命令都可以正常输出转义字符,但包括转义字符的字符串需要用引号(单双引号都可以)扩起来。

1
2
3
% echo 'Hello\n\tWorld'
Hello
World

常用转义字符对照表,不常用的可以去查 ASCII 码表,然后使用 \xnn(如 \x14)。

转义字符 含义 ASCII 码值(十六进制)
\n 换行 0a
\r 回车 0d
\t tab 09
\ \ 5c
` ` 60
\xnn 取决于 nn nn

可以用 hexdump 命令查看字符的 ASCII 码值。

1
2
3
% echo ab= | hexdump -C
00000000 61 62 3d 0a |ab=.|
00000004

还有一些字符是可选转义(通常有特殊含义的字符都是如此)的,比如空格、"'*~$&()[]{};? 等等,即如果在引号里边则无需转义(即使转义也不出错,转义方法都说前边加一个 \),但如果在引号外边则需要转义。谨慎起见,包含半角符号的字符串全部用引号包含即可,可以避免不必要的麻烦。

可以这样检查一个字符在空格外是否需要转义,输出的字符中前边带 \ 的都是需要的。

1
2
3
4
5
% str='~!@#$%^&*()_+-={}|[]:;<>?,./"'
# -r 选项代表忽略字符串中的转义符合
# ${(q)str} 功能是为字符串中的特殊符号添加转义符号
% print -r ${(q)str}
\~\!@\#\$%\^\&\*\(\)_+-=\{\}\|\[\]:\;\<\>\?,./\"

单引号

单引号的左右主要是为了避免字符串里的特殊字符起作用。在单引号中,只有一个字符需要转义,转义符号 \ 。所以如果字符串里包含特殊符号时,最好使用单引号包含起来,避免不必要的麻烦。如果字符串需要包含单引号,可以使用这几种方法。

1
2
3
4
5
6
7
8
9
10
11
# 用双引号包含
% echo "a'b"
a'b
# 用转义符号
% echo a\'b
a'b
# 同时使用单引号和转义符号,用于包含单引号和其他特殊符号的场景
% echo 'a"\'\''b*?'
a"\'b*?

双引号

双引号的作用类似单引号,但没有单引号那么严格,有些特殊字符在双引号里可以继续起作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 以使用变量
% str=abc
% echo "$str"
abc
# 可以使用 $( ) 运行命令
% echo "$(ls)"
git
tmp
# 可以使用 ` ` 运行命令,不建议在脚本里使用 ` `
% echo "`date`"
Mon Aug 28 09:49:11 CST 2017
# 可以使用 $(( )) 计算数值
% echo "$((1 + 2))"
3
# 可以使用 $[ ] 计算数值
% echo "$[1 + 2]"
3

简单说,$ 加各种东西的用法在双引号里都是可以正常使用的,而其他特殊符号(比如 *?>)的功能通常不可用。

反引号

反引号是用来运行命令的,它会返回命令结果,以便保存到变量等等。

1
2
3
4
5
6
7
8
9
10
% str=`ls`
% echo $str
git
tmp
# 完全可以用 $( ) 取代
% str=$(ls)
% echo $str
git
tmp

反引号的功能和 $( ) 功能基本一样,但 $( ) 可以嵌套,而反引号不可以,而且反引号看起来更费事,某些字体中的反引号和单引号差别不大。所以在脚本里不建议使用反引号。

Read More

zsh-字符串常用操作

zsh 的字符串处理功能,要比绝大多数编程语言自带的字符串函数库或者类库要强大(在不依赖外部命令的情况)。同时各种用法也比较怪异,很多时候简洁性和可读性是有矛盾的,很难兼顾。而 shell 的使用场景决定简洁性是不能被牺牲掉的,即使用 Python 这样比较简洁的语言来处理字符串,很多时候也只能写出冗长的代码,而 zsh 经常可以一行搞定(可能有人想到了 Perl,Perl 在处理文本方面确实有比较明显的优势,但使用 Perl 的话也要承担更多的成本),如果再加上适当地使用外部命令,基本可以应付大多数字符串处理场景。因为字符串处理的内容比较丰富,我会分多篇文章写。本篇只涉及最基础和常用的字符串操作,包括字符串的拼接、切片、截断、查找、遍历、替换、匹配、大小写转换、分隔等等。

字符串长度

1
2
3
4
5
6
% str=abcde
% echo $#str
5
# 读取函数或者脚本的第一个参数的长度
% echo $#1

字符串拼接

1
2
3
4
5
6
7
8
9
% str1=abc
% str2=def
% str2+=$str1
% echo $str2
defabc
% str3=$str1$str2
abcdefabc

字符串切片

1
2
3
4
5
6
7
8
% str=abcdef
% echo $str[2,4]
bcd
% echo $str[2,-1]
bcdef
# $1 是文件或者函数的第一个参数
echo ${1[2,4]}

字符串切片还有另一种风格的方法,即 bash 风格,功能大同小异。通常没有必要用这个,而且因为字符位置是从 0 开始算,容易混淆。

1
2
3
4
5
% str=abcdef
% echo ${str:1:3}
bcd
% echo ${str:1:-1}
bcde

字符串截断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
% str=abcdeabcde
# 删除左端匹配到的内容,最小匹配
% echo ${str#*b}
cdeabcde
# 删除右端匹配到的内容,最小匹配
% echo ${str%d*}
abcdeabc
# 删除左端匹配到的内容,最大匹配
% echo ${str##*b}
cde
# 删除右端匹配到的内容
% echo ${str%%d*}
abc

字符串查找

子字符串定位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
% str=abcdef
# 这里用的是 i 的大写,不是 L 的小写
% echo $str[(I)cd]
3
# I 是从右往左找,如果找不到则为 0, 方便用来判断
% (($str[(I)cd])) && echo good
good
# 找不到则为 0
% echo $str[(I)cdd]
0
# 也可以使用小 i,小 i 是从左往右找,找不到则返回数组大小 + 1
% echo $str[(i)cd]
3
% echo $str[(i)cdd]
7

遍历字符

1
2
3
4
5
6
7
8
9
% str=abcd
% for i ({1..$#str}) {
> echo $str[i]
>}
a
b
c
d

字符串替换

按内容替换和删除字符。

1
2
3
4
5
6
7
8
9
10
11
12
% str=abcdefg
# 一对一地替换
% str[2]=1
% echo $str
a1cdefg
# 可以多对多(也包括一对多和多对一)地替换字符,两边的字符数量不需要一致。
# 把第二、三个字符替换成 2345
% str[2,3]=2345
% echo $str
a2345defg

Read More

zsh-变量和语句

格式约定

文中行首的 % 代表 zsh 的命令提示符(类似 bash 的 $,这个是可以自由定义的,具体是什么不重要),行首的 > 代表此行是换行后的输入内容,以 # 开头的为注释(非 root 用户的命令提示符,本系列文章不需要 root 用户),其余的是命令的输出内容。另外某些地方会贴成段的 zsh 代码,那样就省略开头的 %,比较容易分辨。

变量

接触一门新的编程语言,运行完 Hello World 后,首先要了解的基本就是如何定义和使用变量了。有了变量后可以比较变量内容,进而可以接触条件、循环、分支等语句,继而了解函数的用法,更高级的数据结构的使用,更多库函数,等等。这样就大概了解了一门面向过程的语言的基本用法,剩下的可以等到用的时候再查手册。

所以这一篇讲最基本的变量和语句。

zsh 有 5 种变量:整数、浮点数(bash 不支持)、字符串、数组、哈希表(或者叫关联数组或者字典,本系列文章统一使用“哈希表”这一名词),另外还有一些其他语言少有的东西,比如 alias(但主要是交互时使用,编程时基本用不到)。此篇只涉及整数、浮点数、字符串,并且不涉及数值计算和字符串处理等内容。

变量定义

Zsh 的变量多数情况不需要提前声明或者指定类型,可以直接赋值和使用(但哈希表是一个例外)。

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
# 等号两端不能有空格
% num1=123
% num2=123.456
% str1=abcde
# 如果字符串中包含空格等特殊字符,需要加引号
% str2='abc def'
# 也可以用双引号,但和单引号有区别,比如双引号里可以使用变量,而单引号不可以
% str3="abc def $num1"
# 在字符串中可以使用转义字符,单双引号均可
% str4="abc\tdef\ng"
# 输出变量,也可以使用 print
% echo $str1
abcde
# 简单的数值计算
% num3=$(($num1 + $num2))
# (( 中的变量名可以不用 $
% num3=$((num1 + num2))
# 简单的字符串操作
% str=abcdef
# 2 和 4 都是字符在数组的位置,从 1 开始数,逗号两边不能有空格
% echo $str[2,4]
bcd
# -1 是最后一个字符
% echo $str[4,-1]
def

变量比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 比较数值
% num=123
# (( )) 用于数值比较等操作,如果为真返回 0,否则返回 1
# && 后边的语句在前边的语句为真时才执行
# 注意这里只能使用双等号来比较
% ((num == 123)) && echo good
good
# (( 里边可以使用与(&&)或(||)非(!)操作符,同 c 系列语言
% ((num == 1 || num == 2)) && echo good
# 比较字符串
% str=abc
# 比较字符串要用 [[,内侧要有空格,[[ 的具体用法之后会讲到
# 这里双等号可以替换成单等号,可以根据自己的习惯选用
# 本系列文章统一使用双等号,因为和 (( )) 一致,并且使用双等号的常用编程语言更多些
# $str 两侧不需要加双引号,即使 str 未定义或者 $str 中含空格和特殊符号
% [[ $str == abc ]] && echo good
good
# 可以和空字符串 "" 比较,未定义的字符串和空字符串比较结果为真
# [[ 里也可以用 && || !
% [[ $str == "" || $str == 123 ]] && echo good

Read More

Hello World

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

PHP总结

基础篇

PHP如何实现类自动加载?

  • 设置自动加载目录 set_include_path(String $path)。

    1
    set_include_path(dirname(__file__) . '/classes');
  • 修改php.ini文件include_path选项

  • 写一个__autoload($class)方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function __autoload($class) {
    $path = dirname(__FILE__) . "/autoload/$class.php";
    echo $path;
    if (!file_exists($path)) {
    die ("class $class can not load\n");
    }
    include $path;
    }
    $b = new B();
  • 写一个加载器,然后用spl_autoload_register方法注册。

    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
    <?php
    DEFINE('ROOT_PATH', dirname(__FILE__));
    class Loader {
    public static function load($class) {
    $path = null;
    if (substr($class, 0 - strlen('Controller')) == 'Controller') {
    $path = ROOT_PATH . '/Controllers/';
    } else if (substr($class, 0 - strlen('Model')) == 'Model') {
    $path = ROOT_PATH . '/Models/';
    } else {
    $path = ROOT_PATH . '/Components/';
    }
    $path .= "$class.php";
    if (file_exists($path)) {
    include $path;
    return;
    }
    return false;
    }
    }
    spl_autoload_register(array('Loader', 'load'), false, false);
    $controller = new AController();

推荐第四种,原因如下

第一第二种不够灵活

第三种灵活,但是__autoload只能被重写一次

第四种相对灵活,而且在引用第三方差价或者工具的时候有非常明显的优势

include/include_once/require/require_once的区别?

include在文件不存在的时候会打印警告信息然后继续执行,而require会终止程序,相对来说include效率更高

include_once和require_once在加载文件之前都会先判断文件是否已经被加载过,相对来说效率低一些

Read More

对于所有对象都通用的方法

覆盖equals时请遵守通用约定

不覆盖equals方法的几种情况

  • 类的每个实例本质上都是唯一的;例如Thead。
  • 不关心类是否提供了”逻辑相等(logical equality)”的测试功能;例如Rondom。
  • 超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的;例如大多数Set都实现从AbtractSet继承的equals实现。
  • 类是私有的或者包级私有的,可以确定它的equals方法永远不会调用。

什么时候应该覆盖Object.equals呢?如果类具有自己特有的”逻辑相等”概念,而且超类还没有覆盖equals以实现期望的行为,这时候我们就需要覆盖equals方法

覆盖eqauls方法需要遵守的通用约定:

  • 自反性(reflexive)。对于任何非null的引用值x,x.equals(x)必须返回true
  • 对称性(symmetric)。对于任何非null的引用值x和y,当且仅当y.equas(x)返回true时,x.equals(y)必须返回true。
  • 传递性(transitive)。对于任何非null的引用值x、y和z,如果x.equals(y)返回true且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
  • 一致性(consistent)。对于任何非null的引用值x和y,只要equals的比较操作在对象所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true或者false。

Read More

创建和销毁对象

考虑用静态工厂方法代替构造器

对于类而言,为了让客户端获取它的每一个实例,最常用的方法就是提供一个公有的构造器。还有一种方法,也应该在每个程序员的工具箱中占有一席之地。类可以提供一个公有的静态工厂方法,它只是一个返回类的实例的静态方法。下面是一个来自Boolean的简单示例。这个方法将boolean基本类型值转换成了一个Boolean对象引用:

1
2
3
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}

注意,静态工厂方法与设计模式中的工厂方法模式不同。

类可以通过静态工厂方法来提供它的客户端,而不是通过构造器。提供静态方法而不是公有的构造器,这样做有几大优势。

Read More

Vim系列之插入(四)

普通插入

i & a

在普通模式下,按下按键i即可进入插入模式从光标处开始插入;按下a即可进入插入模式从光标后一个字符处开始插入。

行首与行尾插入

I & A

在普通模式下,按下按键I即可移动光标至行首第一个非空白字符处开始插入(相当于按下^再按i);按下按键A即可移动光标至行尾开始插入(相当于按下$再按a)。

另起一行插入

o & O

在普通模式下,按下字母o即可另起一空白行(向下),从行首处开始插入(安装了插件或者语法打开之后会自动缩进排版);按下字母O即可另起一行(向上),从行首处开始插入。

PHP单元测试PHPUnit

简介

PHPUnit is a programmer-oriented testing framework for PHP.
It is an instance of the xUnit architecture for unit testing frameworks.

PHPUnit是一个轻量级的PHP测试框架。它是在PHP5下面对JUnit3系列版本的完整移植,是xUnit测试框架家族的一员(它们都基于模式先锋Kent Beck的设计)。

PHPUnit的依赖

  • dom,PHP默认启用
  • json,PHP默认启用
  • pcre,PHP默认启用
  • reflection,PHP默认启用
  • spl,PHP默认启用
  • tokenizer,PHP默认启用,生成代码覆盖率测试报告用到
  • xdebug,需要自己安装,生成代码覆盖率测试报告用到
  • xmlwriter,PHP默认启用,生成XML格式的报告用到

检查PHP是否启用了这些模块可以使用如下命令

1
php -m | grep '模块名称'

PHPUnit的安装

PHP档案包方式

1
2
3
4
wget https://phar.phpunit.de/phpunit.phar # 下载档案包
chmod +x phpunit.phar # 赋可执行权限
sudo mv phpunit.phar /usr/local/bin/phpunit
phpunit --version # 查看PHPUnit版本

Composer方式

Composer使用参考https://getcomposer.org/

composer文件如下

1
2
3
4
5
{
"require-dev": {
"phpunit/phpunit": "5.0.*"
}
}

编写PHPUnit测试

惯例和基本步骤

  1. 针对类Class的测试写在类ClassTest中。
  2. ClassTest通常继承自PHPUnit_Framework_TestCase。
  3. 测试都是命名为test*的公用方法(也可以在放的文档注释块中使用@test标注将其标记未测试方法)。
  4. 在测试方法内,类似于assertEquals()这样的断言方法用来对实际值与预期值的匹配做出断言

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class HelloWolrdTest extends PHPUnit_Framework_TestCase
{
public function testString()
{
$string = 'Hello, World';
$this->assertEquals('Hello, World', $string);
$this->assertNotEquals('Hello,World', $string);
}
public function testAarry()
{
$arr = array();
$this->assertEmpty($arr);
//$this->assertArrayHasKey('hello', $arr);
$arr['hello'] = 'world';
//$this->assertEmpty($arr);
$this->assertArrayHasKey('hello', $arr);
}
}

执行测试命令

1
phpunit HelloWorldTest

结果

1
2
3
4
5
6
7
PHPUnit 4.8.24 by Sebastian Bergmann and contributors.
..
Time: 103 ms, Memory: 12.50Mb
OK (2 tests, 4 assertions)

Read More