Nested Array in Bash

用了Bash这么久,才知道Bash支持Array。但是却缺乏对嵌套数组,或者是多维数组的支持。自己的实验里面需要用到结构性的数据,这样看起来或者改起来会比较方便,而且因为这部分是用来处理实验结果,需要经常修改,所以不适合放到C++里面去写。
因此就有了需要Bash支持嵌套数组的需求。

最终的解决方法不是很漂亮,但是也足够我自己用了。这个想法的出发点是这样的:

Bash在处理数组的时候会用到IFS这个环境变量。比如这样一段字符

Li,Age*1;Weight*2;Height*3;Friends*Sun^Wang Wang,Age*11;Weight*12;Height*13;Friends*Li^Sun

如果IFS是空格,我们就可以得到两个元素

  • Li,Age*1;Weight*2;Height*3;Friends*Sun^Wang
  • Wang,Age*11;Weight*12;Height*13;Friends*Li^Sun

如果IFS是分号(;),我们可以得到另一个数组

  • Li,Age*1
  • Weight*2
  • Height*3
  • Friends*Sun^Wang Wang,Age*11
  • Weight*12
  • Height*13
  • Friends*Li^Sun

也就是说我们可以通过指定不同的IFS让一段字符成为不同的数组。

所以,我们可以通过对每一层使用不同的IFS来表达一个嵌套数组。比如上面那段字符,我们用不同的IFS字符,先用空格( ),再用逗号(,),之后用分号(;)……
如此做下去就可以达到层层盘剥的效果(-_-!)

在实际使用中,我们只要保证每一层用到的IFS不会在数组内容中出现就行了。
为了能够生成那样一段字符,我们自然需要一些函数做辅助。

具体的代码放到Github上了。目前这个方法虽然有效,但是使用起来仍然不够简洁,看起来如果找不到更好的Bash下的解决方法,就得考虑换用一种20世纪的脚本语言了…

Github: Nested-Array-Bash

rlwrap – 命令行下readline的封装

发现了rlwrap这个好东西。

在Mac OS和Linux下一直都用bash,bash下命令的输入都是通过readline这个库来处理的。也就是说,上下箭头查看历史命令,Ctrl+r反向查找匹配历史输入,以及Ctrl+w, Ctrl+a等等操作都是由readline提供的。rlwrap提供了readline的封装!

rlwrap runs the specified command, 
intercepting user input in order to provide readline's line editing, 
persistent history and completion.

也就是说rlwrap提供一个输入环境,在这个输入环境下可以使用readline的各种功能。如果一个程序在命令行下接受输入,那么用rlwrap直接就可以得到像在bash下输入那样的效果。

比如一个简单的反转输入行的程序

#include 
#include 
#include 

int main(int argc, char **argv)
{
    using namespace std;
    string str;
    cout << "[I] ";
    while (getline(cin,str))
    {   
        reverse(str.begin(), str.end());
        cout << "[O] " << str << endl;
        cout << "[I] ";
    }   
    return 1;
}

编译之后执行

$./a.out 
[I] a + b + c + d
[O] d + c + b + a
[I]

输入完一行,按上箭头是没有回滚到上一条命令这种效果的,更不用说行头行尾跳转了。

如果通过rlwrap输入

$rlwrap ./a.out 
[I] a + b + c + d
[O] d + c + b + a

再试试上下箭头,就和其他使用了readline的程序一致了!

对于一些没有提供readline操作的程序,rlwrap就是必备工具!
以后再写需要交互的bash脚本或者命令行程序就会非常轻松了 :]

Linux Tips (1)

这里记录一些小技巧,比较杂。

1. bash下,x{a,b}会被展开为xa xb,很适合文件备份。
如果你要复制一份a.txt作为备份,到a.txt的路径又太长了,这么写比较方便。

cp /a/long/long/long/path/to/file/a.txt{,bak}

2. vim下,要把某些内容替换成为行号,可以用\=line(“.”)来处理。“.”用来连接行号和其它内容。

:%s/xxxx/\=line(".") . " "/g

3. bash脚本下,如果要写多行到文件里,可以用

cat > file << EOF
file content line 1
file content line 2
file content line 3
file content line 4
EOF

这是一种固定写法,从第二行起,遇到EOF就停止,把之前的内容写到file里。EOF也可以换成别的。

4. Trap SIGINT的时候记得要处理SIGINT信号。

#! /bin/bash

trap "echo 'hello'; exit 1" SIGINT SIGTERM

while [ 1=1 ];
do
    sleep 1
done

exit 1这部分是必须的,否则虽然按下Ctrl + C的时候会显示hello,但是脚本不会被终止。

Use a different delimiter in sed

Think about this, you want to replace “XXX” with a path like “/path/to/YYY” in a file.
Your file looks like this:

XXX
AAA
XXX

Your bash script looks like this:

NEW_PATH="/path/to/YYY"
VAR="XXX"
sed -e '{s/$VAR/$NEW_PATH/g}' your_file

Well, it won’t work, since single quotes ‘ will force bash to keep variables as-is.

Ok, we try to use double quotes ”

NEW_PATH="/path/to/YYY"
VAR="XXX"
sed -e "{s/$VAR/$NEW_PATH/g}" your_file

This one won’t work either.
It is ok for sed to not have the single quotes, the problem is
you have slash in your NEW_PATH variable, which is the default delimiter of sed.

This simple stuff took me about an hour :[

The solution is just use a different delimiter in sed.

NEW_PATH="/path/to/YYY"
VAR="XXX"
sed -e "{s@$VAR@$NEW_PATH@g}" your_file

Sometimes, you still want to use single quotes to keep your special symbols, such as ‘(‘ ‘)’
It is ok for your to mix quotes:

NEW_PATH="/path/to/YYY"
VAR="XXX"
sed -e '{s@\(.*\)@'"$NEW_PATH"'@g}' your_file