test命令

人老了,老记不起,mark一下

测试的标志 代表意义
1. 关于某个档名的『文件类型』判断,如 test -e filename 表示存在否
-e 该『档名』是否存在?(常用)
-f 该『档名』是否存在且为档案(file)?(常用)
-d 该『文件名』是否存在且为目录(directory)?(常用)
-b 该『档名』是否存在且为一个 block device 装置?
-c 该『档名』是否存在且为一个 character device 装置?
-S 该『档名』是否存在且为一个 Socket 档案?
-p 该『档名』是否存在且为一个 FIFO (pipe) 档案?
-L 该『档名』是否存在且为一个连结档?
2. 关于档案的权限侦测,如 test -r filename 表示可读否 (但 root 权限常有例外)
-r 侦测该档名是否存在且具有『可读』的权限?
-w 侦测该档名是否存在且具有『可写』的权限?
-x 侦测该档名是否存在且具有『可执行』的权限?
-u 侦测该文件名是否存在且具有『SUID』的属性?
-g 侦测该文件名是否存在且具有『SGID』的属性?
-k 侦测该文件名是否存在且具有『Sticky bit』的属性?
-s 侦测该档名是否存在且为『非空白档案』?
3. 两个档案之间的比较,如: test file1 -nt file2
-nt (newer than)判断 file1 是否比 file2 新
-ot (older than)判断 file1 是否比 file2 旧
-ef 判断 file1 与 file2 是否为同一档案,可用在判断 hard link 的判定上。 主要意义在判定,两个档案是否均指向同一个 inode 哩!
4. 关于两个整数之间的判定,例如 test n1 -eq n2
-eq 两数值相等 (equal)
-ne 两数值不等 (not equal)
-gt n1 大于 n2 (greater than)
-lt n1 小于 n2 (less than)
-ge n1 大于等于 n2 (greater than or equal)
-le n1 小于等于 n2 (less than or equal)
5. 判定字符串的数据
test -z string 判定字符串是否为 0 ?若 string 为空字符串,则为 true
test -n string 判定字符串是否非为 0 ?若 string 为空字符串,则为 false。
注: -n 亦可省略
test str1 == str2 判定 str1 是否等于 str2 ,若相等,则回传 true
test str1 != str2 判定 str1 是否不等于 str2 ,若相等,则回传 false
6. 多重条件判定,例如: test -r filename -a -x filename
-a (and)两状况同时成立!例如 test -r file -a -x file,则 file 同时具有 r 与 x 权限时,才回传 true。
-o (or)两状况任何一个成立!例如 test -r file -o -x file,则 file 具有 r 或 x 权限时,就可回传 true。
! 反相状态,如 test ! -x file ,当 file 不具有 x 时,回传 true

 

在centos7上为nginx添加ssl

上午boss说要为公司的web服务器加上SSL,我说这个简单,三两下能搞定,关键是发证机构的收费问题。boss说你先去找个免费的证书来,先把服务器调试好,运行没问题,我们再换高大上的证书。于是乎,就这样开始了我的折腾之旅。
在google找到几个免费的crt发证机构,普遍推荐startssl,还有两篇step by step的申请、安装教程,一并粘贴过来:StartSSL免费SSL证书成功申请-HTTPS让访问网站更安全Nginx,Cpanel,DirectAdmin安装StartSSL免费SSL证书。申请流程不一一细表,这两篇文章写得很详细,对着做下来就是。

把startssl生也的.crt文件和.key文件scp到服务器上,这里要注意的是:一定别放到/root目录或者/home/USERNAME目录下,nginx进程去访问别人的家目录,发生”Permission denied”是必然的事。放在nginx的conf目录/etc/nginx/YOURDIR是个比较好的选择。
然后去改相应网站的conf文件,例如:

vim /usr/local/nginx/conf/nginx.conf

在server段中添加如下代码:

 listen       443;
 ssl on;
 ssl_certificate /etc/nginx/YOURDIR/YOURFILE.crt;
 ssl_certificate_key /etc/nginx/YOURDIR/YOURFILE.key;
 ssl_session_timeout 5m;

然后:

systemctl restart nginx.service
Job for nginx.service failed because the control process exited with error code. See "systemctl status nginx.service" and "journalctl -xe" for details.

什么鬼东西?
看了下log:

[emerg] 22981#0: SSL_CTX_use_PrivateKey_file("/etc/nginx/DIR/FILE.key") failed (SSL: error:0906406D:PEM routines:PEM_def_callback:problems getting password error:0906A068:PEM routines:PEM_do_header:bad password read error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib)

原因是在startssl申请的key是要输入密码的,最少10位最多32位,这里提示的就是nginx读取不了我们在startssl上给key设置的密码,最简单的解决办法当然是把这个key的密码去掉。
在stackoverflow上边找到个相似的案例,并给出了解决方法,可以参考下: What does “SSL_CTX_use_PrivateKey_file” “problems getting password error” indicate in Nginx error log?。不同的证书机构发出来的证书扩展名不尽相同,证书机构的F.A.Q给出的解决办法才是标准答案,startssl的链接在这里:How can I decrypt the private key?

openssl rsa -in ssl.key -out ssl.key
systemctl restart nginx.service

nginx最终可以正常运行了,但这还不是最终结局!当我尝试不用https开头去访问网站的时候,网站提示:

400 Bad Request 
The plain HTTP request was sent to HTTPS port

我去,什么鬼。https访问正常了,但http死活不正常。没办法,爬墙继续google。
最后在“阿里云CentOS 6.5系统LNMP环境安装SSL证书”这里找到答案,其它的改fastcgi_param或者upstream的都可以不用管…………

###### 新增一个“server”,保留原80端口,并强制将http协议转换到https协议
server {
    listen 80;
    server_name www.typecodes.com;
    rewrite ^(.*) https://$server_name$1 permanent;
}
###### 在原“server”中,启用支持https协议的443端口,并配置相关信息
server {
    listen 443;
    server_name  www.typecodes.com;

    ssl on;
    # 最后新生成.crt文件
    ssl_certificate /etc/nginx/ssl/typecodes_last.crt;
    # 最开始生成.key文件
    ssl_certificate_key /etc/nginx/ssl/typecodes.key;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_ciphers ALL:!aNULL:!ADH:!eNULL:!LOW:!EXP:RC4+RSA:+HIGH:+MEDIUM; #Disables all weak ciphers
    ssl_protocols SSLv3 TLSv1; #enables SSLv3/TLSv1, but not SSLv2 which is weak and should no longer be used.
    ssl_prefer_server_ciphers on;
    ***************省略其它不变的部分****************
}

重启nginx服务,一切正常。

WordPress禁止非中文评论

/* refused spam */  
function refused_spam_comments( $comment_data ) {  
$pattern = '/[一-龥]/u';  
if(!preg_match($pattern,$comment_data['comment_content'])) {  
err('评论必须含中文!');  
}  
return( $comment_data );  
}  
add_filter('preprocess_comment','refused_spam_comments');

如果你使用ajax评论,请直接将上面的代码复制到主题的functions.php模板即可,如果主题用的不到ajax评论,把err改成wp_die就行
另外第三行的$pattern = ‘/[一-龥]/u’;是个正则表达式,用于表示所有中文字符

来源:http://www.xj123.info/4587.html

解壓縮後的 SELinux 課題

    注:标记一下,值得关注的问题.

如果,鳥哥是說如果,如果因為某些緣故,所以你的系統必須要以備份的資料來回填到原本的系統中,那麼得要特別注意復原後的系統的 SELinux 問題! 尤其是在系統檔上面!例如 /etc 底下的檔案群。SELinux 是比較特別的細部權限設定,相關的介紹我們會在 16 章好好的介紹一下。 在這裡,你只要先知道,SELinux 的權限問題『可能會讓你的系統無法存取某些設定檔內容,導致影響到系統的正常使用權』。

這兩天 (2015/07) 接到一個網友的 email,他說他使用鳥哥介紹的方法透過 tar 去備份了 /etc 的資料,然後嘗試在另一部系統上面復原回來。 復原倒是沒問題,但是復原完畢之後,無論如何就是無法正常的登入系統!明明使用單人維護模式去操作系統時,看起來一切正常~但就是無法順利登入。 其實這個問題倒是很常見!大部分原因就是因為 /etc/shadow 這個密碼檔案的 SELinux 類型在還原時被更改了!導致系統的登入程序無法順利的存取它, 才造成無法登入的窘境。

那如何處理呢?簡單的處理方式有這幾個:

  • 透過各種可行的救援方式登入系統,然後修改 /etc/selinux/config 檔案,將 SELinux 改成 permissive 模式,重新開機後系統就正常了;
  • 在第一次復原系統後,不要立即重新開機!先使用 restorecon -Rv /etc 自動修復一下 SELinux 的類型即可。
  • 透過各種可行的方式登入系統,建立 /.autorelabel 檔案,重新開機後系統會自動修復 SELinux 的類型,並且又會再次重新開機,之後就正常了!
  • 鳥哥個人是比較偏好第 2 個方法,不過如果忘記了該步驟就重新開機呢?那鳥哥比較偏向使用第 3 個方案來處理,這樣就能夠解決復原後的 SELinux 問題囉! 至於更詳細的 SELinux ,我們得要講完程序 (process) 之後,你才會有比較清楚的認知,因此還請慢慢學習,到第 16 章你就知道問題點了! ^_^

    来源:http://linux.vbird.org/linux_basic/0240tarcompress.php#pack

    CentOS7強制使用 GPT 分割表的安裝參數

    Centos7光盘引导安装的话会自动检测计算机硬件,如果检测到的硬盘小于2T则会默认安装MSDOS MBR来引导CentOS,而非GPT.如果想要强制安装GTP则要在开始安装前加入inst.gpt参数,如下图:
    centos7_02
    1.用方向键选中”Install CentOS 7″选项.
    2.按Tab键,CentOS安装引导程序会将安装参数命令行显示在屏幕最下方,并且光标自动定位在默认安装参数行末.
    3.在默认安装参数行末键入空格,添加”inst.gpt”参数.回车.
    后面的安装设置满大街,不再重述了…

    引用来源:http://linux.vbird.org/linux_basic/0157installcentos7.php

    SGID实用案例

    情境模擬題一:假設系統中有兩個帳號,分別是 alex 與 arod ,這兩個人除了自己群組之外還共同支援一個名為 project 的群組。假設這兩個用戶需要共同擁有 /srv/ahome/ 目錄的開發權,且該目錄不許其他人進入查閱。 請問該目錄的權限設定應為何?請先以傳統權限說明,再以 SGID 的功能解析。

  • 目標:瞭解到為何專案開發時,目錄最好需要設定 SGID 的權限!
  • 前提:多個帳號支援同一群組,且共同擁有目錄的使用權!
  • 需求:需要使用 root 的身份來進行 chmod, chgrp 等幫用戶設定好他們的開發環境才行! 這也是管理員的重要任務之一!
  • 首先我們得要先製作出這兩個帳號的相關資料,帳號/群組的管理在後續我們會介紹, 您這裡先照著底下的指令來製作即可:

    [root@study ~]# groupadd project        <==增加新的群組
    [root@study ~]# useradd -G project alex <==建立 alex 帳號,且支援 project
    [root@study ~]# useradd -G project arod <==建立 arod 帳號,且支援 project
    [root@study ~]# id alex                 <==查閱 alex 帳號的屬性
    uid=1001(alex) gid=1002(alex) groups=1002(alex),1001(project) <==確實有支援!
    [root@study ~]# id arod
    uid=1002(arod) gid=1003(arod) groups=1003(arod),1001(project) <==確實有支援!
    

    然後開始來解決我們所需要的環境吧!
    首先建立所需要開發的專案目錄:

    [root@study ~]# mkdir /srv/ahome
    [root@study ~]# ll -d /srv/ahome
    drwxr-xr-x. 2 root root 6 Jun 17 00:22 /srv/ahome
    

    從上面的輸出結果可發現 alex 與 arod 都不能在該目錄內建立檔案,因此需要進行權限與屬性的修改。 由於其他人均不可進入此目錄,因此該目錄的群組應為project,權限應為770才合理。

    [root@study ~]# chgrp project /srv/ahome
    [root@study ~]# chmod 770 /srv/ahome
    [root@study ~]# ll -d /srv/ahome
    drwxrwx---. 2 root project 6 Jun 17 00:22 /srv/ahome
    # 從上面的權限結果來看,由於 alex/arod 均支援 project,因此似乎沒問題了!
    

    實際分別以兩個使用者來測試看看,情況會是如何?先用 alex 建立檔案,然後用 arod 去處理看看。

    [root@study ~]# su - alex       <==先切換身份成為 alex 來處理
    [alex@www ~]$ cd /srv/ahome   <==切換到群組的工作目錄去
    [alex@www ahome]$ touch abcd  <==建立一個空的檔案出來!
    [alex@www ahome]$ exit        <==離開 alex 的身份
    
    [root@study ~]# su - arod
    [arod@www ~]$ cd /srv/ahome
    [arod@www ahome]$ ll abcd
    -rw-rw-r--. 1 alex alex 0 Jun 17 00:23 abcd
    # 仔細看一下上面的檔案,由於群組是 alex ,arod並不支援!
    # 因此對於 abcd 這個檔案來說, arod 應該只是其他人,只有 r 的權限而已啊!
    [arod@www ahome]$ exit
    

    由上面的結果我們可以知道,若單純使用傳統的 rwx 而已,則對剛剛 alex 建立的 abcd 這個檔案來說, arod 可以刪除他,但是卻不能編輯他!這不是我們要的樣子啊!趕緊來重新規劃一下。

    加入 SGID 的權限在裡面,並進行測試看看:

    [root@study ~]# chmod 2770 /srv/ahome
    [root@study ~]# ll -d /srv/ahome
    drwxrws---. 2 root project 17 Jun 17 00:23 /srv/ahome
    
    測試:使用 alex 去建立一個檔案,並且查閱檔案權限看看:
    [root@study ~]# su - alex
    [alex@www ~]$ cd /srv/ahome
    [alex@www ahome]$ touch 1234
    [alex@www ahome]$ ll 1234
    -rw-rw-r--. 1 alex project 0 Jun 17 00:25 1234
    # 沒錯!這才是我們要的樣子!現在 alex, arod 建立的新檔案所屬群組都是 project,
    # 由於兩人均屬於此群組,加上 umask 都是 002,這樣兩人才可以互相修改對方的檔案!
    

    所以最終的結果顯示,此目錄的權限最好是『2770』,所屬檔案擁有者屬於root即可,至於群組必須要為兩人共同支援的project 這個群組才行!

    来源http://linux.vbird.org/linux_basic/0220filemanager.php

    简洁的 Bash Programming 技巧(2)

    1. bash中alias的使用
    alias其实是给常用的命令定一个别名,比如很多人会定义一下的一个别名:

    alias ll='ls -l'
    

    以后就可以使用ll,实际展开后执行的是ls -l。现在很多发行版都会带几个默认的别名,比如:

    alias grep='grep --color=auto'  # 带颜色显示
    alias ls='ls --color=auto' # 同上
    alias rm='rm -i'  # 删除文件需要确认
    

    alias在某些方面确实提高了很大的效率,但是也是有隐患的,这点可以看我以前的一篇文章终端下肉眼看不见的东西。那么如何不要展开alias,而是用本来的意思呢?答案是使用转义:

    \ls
    \grep
    

    在命令前面加一个反斜杠后就可以了。
    这里要插一段故事,前两天我在shell脚本中定义了下面的一个alias,假设位于文件util.sh:

    #!/bin/bash
    ...
    alias ssh='ssh -o StrictHostKeyChecking=no -o LogLevel=quiet -o BatchMode=yes'
    ...
    

    后面这串ssh选项是为了去掉一些warning的信息,不提示输入密码等等。具体可以看ssh的文档说明。我自己测试的时候好好的,当时我同事跑得时候却依然有报Warning。我对比了下我们两个人的用法:

    sh util.sh  # 我的
    ./util.sh   # 他的
    

    大家应该知道,直接./util.sh执行,shell会去找脚本第一行的shebang中给定的解释器去执行改脚本,所以第二种用法相当于直接用bash来执行。那想必是bash/sh对alias是否默认展开这一点上是有区别的了。翻阅了下Bash的man手册,发现可以通过设置expand_aliases选项来打开alias展开的功能,默认在非交互式Shell下是关闭的(什么是交互式登录Shell)。
    修改下util.sh,打开这个选项就Ok了:

    #!/bin/bash
    ...
    # Expand aliases in script
    shopt -s expand_aliases
    alias ssh='ssh -o StrictHostKeyChecking=no -o LogLevel=quiet -o BatchMode=yes'
    ...
    

    2. awk打印除第一列之外的其他列
    awk用来截取输入行中的某几列很有用,当时如果要排除某几列呢?
    例如有如下的一个文件:

    $ cat /tmp/test.txt
    1 2 3 4 5
    10 20 30 40 50
    

    可以用下面的代码解决(来源):

    $ awk '{$1="";print $0}' /tmp/test.txt
     2 3 4 5
     20 30 40 50
    

    但是前面多了一个空格,可以用cut命令稍微调整下:

    $ awk '{$1="";print $0}' /tmp/test.txt | cut -c2-
    2 3 4 5
    20 30 40 50
    

    3. 巧用bash的命令展开功能备份文件
    假设要备份文件/your/path/to/file.list为/your/path/to/file.list.20121106,常规的方法是:

    cp /your/path/to/file.list /your/path/to/file.list.20121106
    

    这样重复写上一长串的路径,是不是很麻烦,这里利用bash的展开特性可以这样做:

    cp /your/path/to/file.list{,.20121106}
    

    /your/path/to/file.list{,.20121106}这一部分会展开为/your/path/to/file.list /your/path/to/file.list.20121106,再将此传给cp命令,就达到了与前面同样的效果。(思路同ls *)。具体可以man bash中的Brace Expansion这一段。

    4. 命令行下使用ctrl+x ctrl+e来编辑当前命令
    这个技巧来自最牛B的 Linux Shell 命令系列连载(二)。使用方法是键入命令之后,再按ctrl+x ctrl+e可以打开一个编辑器来编辑命令,默认是使用emacs。你也可以通过在~/.bashrc中添加以下这一行,将编辑器换成vim:

    export EDITOR='vim'
    

    为什么推荐这一条呢?对于一般的命令(这里指的是长度很短的命令)其实这个技巧没什么用处,我用方向键移一下就OK了,但是有时候(尤其是运维的一些命令)有些命令长度特别长,一堆参数,如果直接在命令行修改其实风险很高的(可以通过在命令的开头加上一个#号来规避这个风险,Bash将当前的命令当成注释不执行),而且方向键一个一个迁移非常不方便(当然有类似ctrl+x ctrl+e这种预设的快捷键来操作,可以看bind -p)。
    像使用ctrl+x,ctrl+e打开vim来编辑命令在这种场景有两种好处:
    a. 可以方便的用熟悉的编辑器高效地修改命令;
    b. 有一个确认的过程,无误后,退出vim才执行命令。
    不过我不是很推荐最牛B的 Linux Shell 命令 系列连载中的一些对历史命令的技巧,虽然方便,但是风险很高,因为没有一个确认的过程,是执行将历史命令调出就执行了。

    5. 你知道sed的这个特性吗?
    假设一个文件的每一行为一个路径:

    [Tue Nov 06 06:33 PM] [kodango@devops] ~ 
    $ cat /tmp/test.txt
    /home/kodango/hello
    /home/kodango/hello/world
    /home/kodango/good
    /home/kodango/good/bye
    

    现在要把/home/kodango/good替换成/home/kodango/bad,普通的作法是:

    [Tue Nov 06 06:35 PM] [kodango@devops] ~ 
    $ sed -n 's/\/home\/kodango\/good/\/home\/kodango\/bye/p' /tmp/test.txt 
    /home/kodango/bye
    /home/kodango/bye/bye
    

    因为路径中的分隔符与sed的替换命令的分隔符都是’/’,所以需要转义,非常麻烦。幸运的是,sed可以更改分隔符,例如使用#:

    [Tue Nov 06 06:34 PM] [kodango@devops] ~ 
    $ sed -n 's#/home/kodango/good#/home/kodango/bad#p' /tmp/test.txt 
    /home/kodango/bad
    /home/kodango/bad/bye
    

    这样就清爽多了。
    补充,如果是在地址对中使用,首个分隔符前面要加反斜杠:

    $ sed -n '\#/home/kodango/#p' /tmp/test.txt 
    /home/kodango/hello
    /home/kodango/hello/world
    /home/kodango/good
    /home/kodango/good/bye
    

    参见:Using different delimiters in sed

    6. 合并连续重复的字符(即squeeze操作)
    例如要合并一个字符串中连续的多个空格,假设字符串为’print hello, world’。
    第一种方法,使用sed命令,扫描整个字符串,替换2个以上的空格为1格:

    $ echo 'print  hello,   world  ' | sed -r 's/ {2,}/ /g'
    print hello, world 
    

    第二种方法,使用tr命令的-s选项,专门就是为了合并连续重复的字符:

    $ echo 'print  hello,   world  ' | tr -s ' '
    print hello, world 
    

    第三种方法,使用awk的域赋值来完成该目的:

    $ echo 'print  hello,   world  ' | awk '$1=$1'
    print hello, world
    

    对已经存在的域例如$1,$2..进行赋值,会导致awk重新使用OFS输出分隔符重组$0,关于这一点的详细说明见sosodream同学的博文Awk里的域赋值操作和部分源码解析($1=$1,$0=$0,FS,OFS)

    7. 将文本中某列相同的行输出到不同的文件中
    标题有点绕口,我们以实际例子来讲解,假设我们有以下的一个文件:

    $ cat /tmp/test.txt
    a char
    1 int
    2 int
    b char
    abc string
    

    我们的目标是将该文本中的行按第二列的值归类,并且输出到相应的文件中,文件名为第二列的名称。例如第2行、第3行会输出到int.txt文件中,而第1行、第4行则输出到char.txt,以此类推。
    我没有找到其它简单的方法,只找到一种用awk来处理的方法:

    [Wed Nov 07 07:31 PM] [kodango@devops] ~/workspace 
    $ awk '{print $1 > $2 ".txt"}' /tmp/test.txt
    

    我们来检查结果:

    [Wed Nov 07 07:34 PM] [kodango@devops] ~/workspace/output 
    $ grep -nH . *
    char.txt:1:a
    char.txt:2:b
    int.txt:1:1
    int.txt:2:2
    string.txt:1:abc
    

    8. 用exec命令来完成重定向
    以一个简单的例子开始,现在需要一个脚本,它可以接受一个文件名作为参数,然后按行读取该文件的内容并打印到标准输出。如果不指定文件名,则默认从标准输入读。首先按上面的功能需求写出一个可以完成功能的脚本:

    [Sat Nov 10 12:16 AM] [kodango@devops] ~/workspace 
    $ cat test.sh 
    
    filename=$1
    
    if [ -z "$filename" ]; then
        while read line; do
            echo $line
        done
    else
        while read line; do
            echo $line
        done < $filename
    fi
    

    如果换exec来实现重定向,可以把脚本写得更优雅:

    $ cat test1.sh 
    
    filename=$1
    
    if [ -n "$filename" ]; then
        exec 0< $filename
    fi
    
    while read line; do
        echo $line
    done
    

    这里的关键在第5行代码,exec命令不仅可以用于执行命令,还可以用于打开、关闭或者复制文件描述符,这里就是利用exec将指定的文件名打开重定向到标准输入。类似地可以用exec >$filename将文件重定向到标准输出。我们可以在命令行上做一个试验:

    [Sat Nov 10 12:26 AM] [kodango@devops] ~ 
    $ exec 3>&1                   # 首先将fd 3重定向到标准输出,作为标准输出的一个备份
    
    $ ls /proc/629/fd/{1,3} -l    # 现在fd 3和fd 1指向同一个设备文件
    lrwx------ 1 kodango kodango 64 Nov 10 00:26 /proc/629/fd/1 -> /dev/pts/1
    lrwx------ 1 kodango kodango 64 Nov 10 00:26 /proc/629/fd/3 -> /dev/pts/1
    
    $ exec >stdout               # 现在把标准输出重定向到stdout这个文件中
    
    $ ls /proc/629/fd/1 -l        # 如果你此刻在同一个终端下执行本命令是没有返回的
    
    $ ls /proc/629/fd/1 -l        # 现在重新打开一个终端看看,确实已经重定向到stdout这个文件
    l-wx------ 1 kodango kodango 64 Nov 10 00:26 /proc/629/fd/1 -> /home/kodango/stdout
    
    $ exec 1>&3                   # 现在重新把标准输出重定向到之前备份的fd 3上
    $ ls /proc/629/fd/{1,3} -l  # 现在屏幕可以看到输出了,但是fd 3这个描述符还打开,需要关闭
    lrwx------ 1 kodango kodango 64 Nov 10 00:26 /proc/629/fd/1 -> /dev/pts/1
    lrwx------ 1 kodango kodango 64 Nov 10 00:26 /proc/629/fd/3 -> /dev/pts/1
    
    $ exec 3>&-                   # 关闭fd 3
    $ ls /proc/629/fd/3 -l
    ls: cannot access /proc/629/fd/3: No such file or directory
    
    $ cat stdout                  # 检查stdout文件,确实有之前被吃掉的输出
    l-wx------ 1 kodango kodango 64 Nov 10 00:26 /proc/629/fd/1 -> /home/kodango/stdout
    

    关于I/O重定向的更详细的说明,可以看I/O Redirection,这里有很多例子讲解了各种I/O重定向的用法,包括exec来改变重定向。
    这一点在while read; do xxx; done < file内部仍需要从标准输入读取内容时非常有用,此时必须要将循环外部的重定向和内部的剥离开来。 9. 引号之间的区别 Shell中比较让人抓狂的是各种引号的处理,其中,反引号(`cmd`)是最容易掌握的,它其实和$(cmd)是差不多的。 引号的作用有几点,一个是为了将多个因为空格或者回车等分隔符隔开的字符串合在一起,避免被命令行解析分开,例如"one two three"就是一整个字符串,而不是像one two three会被解析成三个单独的字符串;另外一方面,引号可以让一些特殊符号保持原义。 其中,单引号的处理是比较简单的,被单引号包括的所有字符都保留原有的意思,例如'$a'不会被展开, '`cmd`'也不会执行命令;而双引号,则相对比较松,在双引号中,以下几个字符$, `, \依然有其特殊的含义,比如$可以用于变量展开, 反引号`可以执行命令,反斜杠\可以用于转义。但是,在双引号包围的字符串里,反斜杠的转义也是有限的,它只能转义$, `, ", \或者newline(回车)这几个字符,后面如果跟着的不是这几个字符,只不会被黑底,反斜杠会被保留,例如: [code lang="php"] $ echo "\$,\",\`,\',\t" $,",`,\',\t [/code] 双引号内可以直接包含单引号,而且单引号也没有如上据说的特殊含义,所以像"var='$var'"中$var还是会被展开的,而不要以为简单地认为在单引号内部就不会展开了。如果双引号内部包含感叹号!就比较头痛了,感叹号是用于命令行历史展开,例如!!展开为上一次执行的命令。你可以试试双引号中包含!: [code lang="php"] [Sat Nov 10 07:39 PM] [kodango@devops] ~ $ echo "!" -bash: !: event not found $ echo "\!" \! [/code] 可见,即使你用反斜杠也没办法转义,除非你把历史展开功能关闭(在脚本里面是没有问题的,默认是关闭的)。 [code lang="php"] [Sat Nov 10 07:50 PM] [kodango@devops] ~ $ set +o histexpand [Sat Nov 10 07:50 PM] [kodango@devops] ~ $ echo "!" ! [/code] 当然,感叹号可以用在单引号里面。 [code lang="php"] [Sat Nov 10 07:50 PM] [kodango@devops] ~ $ set -o histexpand [Sat Nov 10 07:51 PM] [kodango@devops] ~ $ echo '!' ! [/code] 到此为止,其实双引号和单引号的区别已经说得差不多了。不过还可以再说几个特殊的用法,前面说过可以在双引号内部使用单引号,你有想过在单引号里面使用单引号吗? [code lang="php"] $ echo '\'' > [/code] 是不是发现不能用,因为单引号中反斜杠是没有转义的效果的,任何字符都没有特殊的含义。那就没有办法了吗?方法总是有的,可以在第一个单引号前面加个$符号: [code lang="php"] $ echo $'\'' ' [/code] 这又是另外一种神奇的用法了,我放到下一点讲。 关于这一点的内容,具体可以看以下两份资料: a. http://www.gnu.org/software/bash/manual/html_node/Quoting.html#Quoting
    b. http://tldp.org/LDP/abs/html/quoting.html

    10. 特殊用法$’string’
    前面一点中已经介绍了 $’string’这种用法,比如 $’\”,之所以可以这样用,通俗地讲,就是在这种语法里一些转义字符串是被认可的,事实上有效地的转义底字符串列表可以看这里,例如\b,\’,\n,\f,\nnn,\xhh等等,是不是很熟悉。
    $’string’的这个特性,其实为我们提供了一种很有用的技巧:

    $ echo $'\x41'
    A
    

    他可以将ASCII对应的字符赋值给某个变量或者输出。

    11. 用双引号比不用更加安全
    双引号除了前面第10点讲到的去除特殊涵义的作用外,还可以避免字符串被分隔解析,例如:

    $ echo `ls -l`
    total 4.0K -rw-r--r-- 1 kodango kodango 4 Nov 10 20:09 1 -rw-r--r-- 1 kodango kodango 0 Nov 10 20:09 2
    $ echo "`ls -l`"
    total 4.0K
    -rw-r--r-- 1 kodango kodango 4 Nov 10 20:09 1
    -rw-r--r-- 1 kodango kodango 0 Nov 10 20:09 2
    

    前者没有加双引号,ls -l输出行之间的回车就被吃掉了。原因是,当ls -l返回的结果传递给echo之前,会先被shell进行参数解析,而shell是用IFS定义的分隔符来分隔字符串的,一般包括\n,所以它把解析后的结果再传递给echo,就成为echo “line 1….” “line 2…”这种形式了,结果就像上面一样。
    而用双引号包括起来可以避开字符串被拆开解析,因为shell认为它是一个单独的字符串。所以一般情况下,多用引号包括变量是好的,”$var”比$var更安全。

    12. 显示一个文件并且在每行开头添加行号
    有两种做法,第一种借助cat和nl命令来完成:

    $ cat test.txt | nl
         1	line 1 
         2	line 2
    

    另外一种做法是用sed命令:

    $ sed '=' test.txt | sed 'N;s/\n/\t/'
    

    还有一种方法是通过cat -n或者cat -b命令,两者的区别是后者不会给空行增加行号,感谢kashu同学补充。

    13. 命令行键映射,编辑模式
    命令行下默认是emacs的keymap,对于不会emacs的人来说真是灾难,完全不知道各种ctrl+x键是做什么的,可以通过执行以下命令切换到vi模式:

    set -o vi
    

    在这种模式下,就可以用熟悉的vi命令了,默认输入命令的是在insert模式,按ESC键可以切换到命令模式,这点和vim是一样的,熟悉vim的人很快就可以上手。
    之前介绍过命令行下使用ctrl+x ctrl+e来编辑当前命令,而在vi模式下,可以在命令模式下直接键入v。还有,如果不想执行当前输入的命令,可以在命令模式下按#号键,它会在当前命令当作注释而不执行(在命令开头添加#号)。
    更多vi模式的介绍可以参见Working Productively in Bash’s Vi Command Line Editing Mode,作者还给了一份Vi Editing Mode Cheat Sheet留作参考。
    如果你想将vi模式作为默认的编辑模式,可以将set -o vi写入到~/.bashrc文件中。当然,在运维的线上生产环境这样做是不合适的,你只能手动输入切换了。不过,如果你选择的ssh管理客户端比较高级的话,应该可以避免每次手动输出。比如我用的是xshell,可以通过设置Login script在每次登录的时候自动执行命令,或者将命令添加到quick command set,然后调出quick command set toolbar,手动点击按钮切换。这两种方法结合起来就几乎同写入到~/.bashrc一样的方便了。

    14. 分别输出两个文件相同的行和不同的行
    假设我们有以下两个文件:

    $ echo test{,2}.txt;paste test{,2}.txt
    test.txt test2.txt
    line 1 	line 11
    line 2	line 2
    

    如果要输出两个文件之间相同的行,只有test.txt拥有的行以及只有test2.txt拥有的行,怎么做?首先可以使用grep -f:

    $ grep -f test{,2}.txt
    line 2
    $ grep -vf test{,2}.txt
    line 11
    $ grep -vf test{2,}.txt
    line 1 
    

    还有一种选择是comm命令,这个命令就是专门用于比较文件的: comm – compare two sorted files line by line。
    使用方法也很简单,comm比较两个排序好的文件返回的结果有三列,第一列是只有在文件A中有的行,第二列是只有在文件B中有的行,第三列则是两个文件共有的行:

    $ comm test.txt test2.txt                
    line 1 
    	line 11
    		line 2
    

    要得到最初要求的结果,则只需要取相应的列就可以了。comm命令非常人性化地考虑到这个需求:

    $ comm test.txt test2.txt -1 -2
    line 2
    $ comm test.txt test2.txt -2 -3
    line 1 
    $ comm test.txt test2.txt -1 -3
    line 11
    

    其中,=1, -2与-3这个参数分别表示不输出第1、2或者3列。

    15. 获取被source的脚本的名称
    一般的情况下,脚本的名称可以通过$0获取,但是这在被source导入的脚本中却不成立。假设A脚本中source了B脚本,那么它是把B的代码导入到A的环境中直接执行的,因此A和B的代码其实是在同一个执行环境下分不开的,B的代码中访问到的$0,甚至$1, $2等位置参数都是与A脚本是一致的。
    因此$0并非是被导入的脚本的名称,实际上,Bash将被source的脚本名称保存在一个叫BASH_SOURCE的数组中,该数组的第一个元素正是当前被source的脚本的名称。该变量与我在bash获取当前函数名中介绍的FUNCNAME是类似的,当一个脚本被source时,它的名称就被压入到这个数组的第一个位置上,举个实际的例子,假设有三个脚本a.sh,b.sh,c.sh,它们的内容如下所示:

    $ cat a.sh 
    . ./b.sh
    echo "\$0=$0"
    echo "\${BASH_SOURCE[0]}=${BASH_SOURCE[0]}"
    echo "\$BASH_SOURCE=(${BASH_SOURCE[@]})"
    
    $ cat b.sh 
    . ./c.sh
    . ./c.sh
    echo "\$0=$0"
    echo "\${BASH_SOURCE[0]}=${BASH_SOURCE[0]}"
    echo "\$BASH_SOURCE=(${BASH_SOURCE[@]})"
    
    $ cat c.sh 
    $ cat c.sh 
    echo "\$0=$0"
    echo "\${BASH_SOURCE[0]}=${BASH_SOURCE[0]}"
    echo "\$BASH_SOURCE=(${BASH_SOURCE[@]})"
    

    现在执行a.sh这个脚本,实际的输出是(为了方便理解,我在实际的输出中加了一些注释和空行):

    $ bash a.sh
    # c.sh的输出
    $0=a.sh
    ${BASH_SOURCE[0]}=./c.sh
    $BASH_SOURCE=(./c.sh ./b.sh a.sh)
    
    # b.sh的输出
    $0=a.sh
    ${BASH_SOURCE[0]}=./b.sh
    $BASH_SOURCE=(./b.sh a.sh)
    
    # a.sh的输出
    $0=a.sh
    ${BASH_SOURCE[0]}=a.sh
    $BASH_SOURCE=(a.sh)
    

    此外,我们还可以利用BASH_SOURCE的值,在脚本中判断是被直接执行还是被导入:

    if [ -n "$BASH_SOURCE" -a "$BASH_SOURCE" != "$0" ]
    then
        echo "be sourced by other scripts"
    else
        echo "be run in shell"
    fi
    

    16. ${}参数展开
    我们知道${parameter}是展开变量parameter这个值,在上一篇简洁的bash编程技巧中也曾经介绍过${parameter:-word}这种用法,用于给变量赋一个默认值。
    事实上除此之外,参数展开还有许多形式,在此之前,首先要说明一下变量的几种值的形式:
    1. unset: 变量未设置,即变量从未声明,或者被unset命令重置;
    2. null: 变量声明但未被赋值(var=)或者被赋值成空(var=””);
    3. not null: 变量被赋值;
    unset和null在参数展开的时候还是有很大的区别的,以下是参数展开的各种形式:
    1. ${parameter:-word}:假如parameter为unset或者null,则展开后返回word的值;
    2. ${parameter-word}:假如parameter为unset时,则展开后返回word的值;
    3. ${parameter:=word}:假如parameter为unset或者null,将word赋值给parameter;
    4. ${parameter=word}:假如parameter为unset,将word赋值给parameter;
    5. ${parameter:?word}:假如parameter为unset或者null,则将word作为错误输出到标准输出;
    6. ${parameter?word}:假如parameter为unset,则将word作为错误输出到标准输出;
    7. ${parameter:+word}:假如parameter为unset或者null,则不做展开,返回为空;(刚好与:-相反)
    8. ${parameter:word}:假如parameter为unset,则不做展开,返回为空;(刚好与-相反)
    上面其实准确地应该是分成2组,一组带:,一组不带:,不带:的这组更加严格,只检查unset这种情况。以:+为例子, unset的情况均无返回:

    $ unset var && echo ${var:+hello}
    
    $ unset var && echo ${var+hello}
    

    当var为空时:

    $ var= && echo "${var:+hello}"
    
    $ var= && echo "${var+hello}"
    hello
    

    当var为非空时:

    $ var=1 && echo "${var:+hello}"
    hello
    $ var=1 && echo "${var+hello}"
    hello
    

    关于参数展开的具体内容可以参考Bash Man手册中的Parameter Expansion这一节。

    17. 冒号的多种使用场景
    冒号是一个比较奇怪的符号,它的用途有很多,这里介绍几种常用的:
    1. 内置命令null command:nop,表示什么都不做,也可以被当作true值使用;

    $ :
    $ echo $?    # return 0
    

    它也可以在循环中当作true值,例如:

    while :; do   # 等价于 while true; do
        take-some-action
    done
    
    if condition
    then :
    else 
        take-some-action
    fi
    

    2. 占位符
    冒号可以在很多场景下充当占位符,例如之前介绍的${parameter=var},如果直接执行会报错,表示找不到命令;这时可以借用冒号来完成赋值:

    : ${parameter=var}
    

    同样地,可以来判断变量是否赋值:

    : ${parameter1?} ${parameter2?}
    

    更多其它用法可以看ABS的Special Characters这一节。

    18. 扩展的括号展开功能
    这个功能不能说鸡肋,也可以了解下:

    $ echo {0..3}
    0 1 2 3
    $ echo {z..a}
    z y x w v u t s r q p o n m l k j i h g f e d c b a
    $ echo {a..z}
    a b c d e f g h i j k l m n o p q r s t u v w x y z
    

    19. [[]]比[]作条件测试更安全
    [[]]的功能比[]更加多,使用起来也更加安全。
    1. 首先[[]]内部不会发生文件名展开和单词分隔。
    例如:

    $ touch hello\ world
    $ [[ -f $file ]] && echo yes
    yes
    $ [ -f $file ] && echo yes
    -bash: [: hello: binary operator expected
    

    2. 进制之间自动转化
    当一个十进制与八进制做比较时,会自动计算两个数的值,统一后做比较:

    $ o=017
    $ h=0x0f
    $ [[ $o -eq $h ]] && echo yes
    yes
    $ [[ $o -eq 15 ]] && echo yes
    yes
    

    3. [[]]支持&&,||等运算符

    $ a=1;b=3
    $ [[ $a > 0 && $b < 4 ]] && echo yes
    yes
    

    20. 获取Bash脚本的最后一个参数
    我们都知道可以用$0,$1等来获取传递给脚本或者函数的参数,也可以用$*或者$@获取所有的参数,但是如果我只想要获取最后一个参数呢?
    首先,你可能想到用遍历地方法(这里为了方便,我们使用set命令来设置位置参数):

    $ set -- arg1 arg2 arg3
    $ for i in $@; do :; done
    $ echo $i
    arg3
    

    这里的循环什么事情都没做,我用冒号(:)完成这个任务;循环结束后, $i就是保存着最后一个参数的值。
    下面是两种更加简单的方法的:

    $ echo ${@: -1}
    $ echo ${!#}
    

    上面的第一种方法事实上就是Parameter Expansion中的${parameter:offset:length}这种形式,只不过offset为-1表示最后一个元素,忽略length表明是从offset开始往后直到最后一个元素,即只取最后一个元素。这里要注意的一点是,在冒号和短横之间的空格不能少,否则就变成15. ${}参数展开中介绍的${parameter:-var}这种用法了。
    而第二种方法则是indirect referencing的一种表现,#这个特殊的变量存放参数的个数,!#则是对最后一个变量的引用。

    21. Bash中的引用(indirect referencing)
    有没有想法在Bash中也可以达到C++引用的效果?你可能不知道,但是你可能曾经有这种需求,我就有过:
    有时候,我想要一个变量存放另外一个变量的名称,然后在后面我想通过这个变量的名称引用它的值
    例子是这样的:

    $ a=b
    $ b=1
    $ echo $a
    b
    $ eval "echo \$$a"
    1
    

    但是利用indirect referencing的用法,你可以这样获取b的值:

    $ echo ${!a}
    1
    $ b=2
    $ echo ${!a}
    2
    

    很奇怪的一种用法,关于indirect referencing你可以查看这里或者这里。

    来源:http://kodango.com/simple-bash-programming-skills-2

    简洁的 Bash Programming 技巧(1)

    下面这几条是我自己在写shell代码的时候,比较喜欢的几种写法,抛砖引玉。

    1. 检查命令执行是否成功
    第一种写法,比较常见:

    echo abcdee | grep -q abcd
    
    if [ $? -eq 0 ]; then
        echo "Found"
    else
        echo "Not found"
    fi
    

    简洁的写法:

    if echo abcdee | grep -q abc; then
        echo "Found"
    else
        echo "Not found"
    fi
    

    当然你也可以不要if/else,不过这样可读性比较差:

    [Sun Nov 04 05:58 AM] [kodango@devops] ~/workspace 
    $ echo abcdee | grep -q abc && echo "Found" || echo "Not found"
    Found
    

    2. 将标准输出与标准错误输出重定向到/dev/null
    第一种写法,比较常见:

    grep "abc" test.txt 1>/dev/null 2>&1
    

    常见的错误写法:

    grep "abc" test.txt 2>&1 1>/dev/null
    

    简洁的写法:

    grep "abc" test.txt &> /dev/null
    

    3. awk的使用
    举一个实际的例子,获取Xen DomU的id。
    常见的写法:

    sudo xm li | grep vm_name | awk '{print $2}'
    

    简洁的写法:

    sudo xm li | awk '/vm_name/{print $2}'
    

    4. 将一个文本的所有行用逗号连接起来
    假设文件内容如下所示:

    [Sat Nov 03 10:04 PM] [kodango@devops] ~/workspace 
    $ cat /tmp/test.txt 
    1
    2
    3
    

    使用Sed命令:

    [Sat Nov 03 10:14 PM] [kodango@devops] ~/workspace 
    $ sed ':a;$!N;s/\n/,/;ta' /tmp/test.txt 
    1,2,3
    

    简洁的写法:

    [Sat Nov 03 10:04 PM] [kodango@devops] ~/workspace 
    $ paste -sd, /tmp/test.txt 
    1,2,3
    

    5. 过滤重复行
    假设文件内容如下所示:

    [Sat Nov 03 10:16 PM] [kodango@devops] ~/workspace 
    $ sort /tmp/test.txt 
    1
    1
    2
    3
    

    常用的方法:

    [Sat Nov 03 10:16 PM] [kodango@devops] ~/workspace 
    $ sort /tmp/test.txt | uniq
    1
    2
    3
    

    简单的写法:

    [Sat Nov 03 10:16 PM] [kodango@devops] ~/workspace 
    $ sort /tmp/test.txt -u
    1
    2
    3
    

    6. grep查找单词
    假设一个文本的每一行是一个ip地址,例如

    [Sat Nov 03 10:20 PM] [kodango@devops] ~/workspace 
    $ cat /tmp/ip.list 
    10.0.0.1
    10.0.0.12
    10.0.0.123
    

    使用grep查找是否包括10.0.0.1这个ip地址。常见的写法:

    [Sat Nov 03 10:22 PM] [kodango@devops] ~/workspace 
    $ grep '10.0.0.1\>' /tmp/ip.list 
    10.0.0.1
    

    简单的方法(其实这方法不见得简单,只是为了说明-w这个参数还是很有用的)

    [Sat Nov 03 10:23 PM] [kodango@devops] ~/workspace 
    $ grep -w '10.0.0.1' /tmp/ip.list 
    10.0.0.1
    

    顺便grep的-n/-H/-v/-f/-c这几参数都很有用。

    7. 临时设置环境变量
    常见的写法:

    [Sat Nov 03 10:26 PM] [kodango@devops] ~/workspace 
    $ export LC_ALL=zh_CN.UTF-8 
    
    [六 11月 03 10:26 下午] [kodango@devops] ~/workspace 
    $ date
    2012年 11月 03日 星期六 22:26:55 CST
    

    简洁的写法:

    [六 11月 03 10:26 下午] [kodango@devops] ~/workspace 
    $ unset LC_ALL
    
    [Sat Nov 03 10:27 PM] [kodango@devops] ~/workspace 
    $ LC_ALL=zh_CN.UTF-8 date 
    2012年 11月 03日 星期六 22:27:43 CST
    

    在命令之前加上环境变更的设置,只是临时改变当前执行命令的环境。

    8. $1,$2…等位置参数的使用
    假设只想使用$2,$3..这几个参数,常见的做法是:

    shift
    echo "$@"
    

    为什么不这样写呢?

    echo "${@:2}"
    

    9. 退而求其次的写法
    相信大家会有这种需求,当一个参数值没有提供时,可以使用默认值。常见的写法是:

    arg=$1
    
    if [ -z "$arg" ]; then
       arg=0
    fi
    

    简洁的写法是这样的:

    arg=${1:-0}
    

    10. bash特殊参数–的用法
    假设要用grep查找字符串中是否包含-i,我们会这样尝试:

    [Sat Nov 03 10:45 PM] [kodango@devops] ~/workspace 
    $ echo 'abc-i' | grep "-i"
    Usage: grep [OPTION]... PATTERN [FILE]...
    Try 'grep --help' for more information.
    
    [Sat Nov 03 10:45 PM] [kodango@devops] ~/workspace 
    $ echo 'abc-i' | grep "\-i"
    abc-i
    

    简洁的方法是:

    [Sat Nov 03 10:45 PM] [kodango@devops] ~/workspace 
    $ echo 'abc-i' | grep -- -i
    abc-i
    

    bash中–后面的参数不会被当作选项解析。

    11. 函数的返回值默认是最后一行语句的返回值

    # Check whether an item is a function
    # $1: the function name
    # Return: 0(yes) or 1(no)
    function is_function()
    {
        local func_name=$1
        test "`type -t $1 2>/dev/null`" = "function"
    }
    

    不要画蛇添足再在后面加一行return $?了。

    12. 将printf格式化的结果赋值给变量
    例如将数字转换成其十六进制形式,常见的写法是:

    [Sat Nov 03 10:55 PM] [kodango@devops] ~/workspace 
    $ var=$(printf '%%%02x' 111)
    

    简单的写法是:

    [Sat Nov 03 10:54 PM] [kodango@devops] ~/workspace 
    $ printf -v var '%%%02x' 111 
    

    看看printf的help:

    [Sat Nov 03 10:53 PM] [kodango@devops] ~/workspace 
    $ help printf | grep -A 1 -B 1 -- -v
    printf: printf [-v var] format [arguments]
        Formats and prints ARGUMENTS under control of the FORMAT.
    --
        Options:
          -v var	assign the output to shell variable VAR rather than
        		display it on the standard output
    

    13. 打印文件行
    打印文件的第一行:

    head -1 test.txt
    

    打印文件的第2行:

    sed -n '2p' test.txt
    

    打印文件的第2到5行:

    sed -n '2,5p' test.txt
    

    打印文件的第2行始(包括第2行在内)5行的内容:

    sed -n '2,+4p' test.txt
    

    打印倒数第二行:

    $ tail -2 test.txt | head -1
    $ tac test.txt | sed -n '2p'
    

    14.善用let或者(())命令做算术运算
    如何对一个数字做++运算,可能你会这样用:

    a=1
    a=`expr a + 1`
    

    为何不用你熟悉的:

    a=1
    let a++
    let a+=2
    

    15. 获取软连接指定的真实文件名
    如果你不知道,你可能会这样获取:

    [Sat Nov 03 11:12 PM] [kodango@devops] ~/workspace 
    $ ls -l /usr/bin/python | awk -F'->' '{print $2}' | tr -d ' '
    /usr/bin/python2
    

    如果你知道有一个叫readlink的命令,那么:

    [Sat Nov 03 11:13 PM] [kodango@devops] ~/workspace 
    $ readlink /usr/bin/python
    /usr/bin/python2
    

    16. 获取一个字符的ASCII码

    [Sat Nov 03 11:14 PM] [kodango@devops] ~/workspace 
    $ printf '%02x' "'+"
    2b
    [Sat Nov 03 11:30 PM] [kodango@devops] ~/workspace 
    $ echo -n '+' | od -tx1 -An | tr -d ' '
    2b
    

    17. 清空一个文件
    常见的用法:

    echo "" > test.txt
    

    简单的写法:

    > test.txt
    

    18 不要忘记有here document
    下面一段代码:

    grep -v 1 /tmp/test.txt | while read line; do
        let a++
        echo --$line--
    done
    
    echo a:$a
    

    执行后有什么问题吗?

    [Sun Nov 04 05:35 AM] [kodango@devops] ~/workspace 
    $ sh test.sh 
    --2--
    --3--
    a:
    

    发现a这个变量没有被赋值,为什么呢?因为管道后面的代码是在在一个子shell中执行的,所做的任何更改都不会对当前shell有影响,自然a这个变量就不会有赋值了。

    换一种思路,可以这样做:

    grep -v 1 /tmp/test.txt > /tmp/test.tmp
    
    while read line; do
        let a++
        echo --$line--
    done < /tmp/test.tmp
    
    echo a:$a
    rm -f /tmp/test.tmp
    

    不过多了一个临时文件,最后还要删除。这里其实可以用到here document:

    while read line2; do
        let b++
        echo ??$line2??
    done << EOF
    `grep -v 1 /tmp/test.txt`
    EOF
    
    echo b: $b
    

    here document往往用于需要输出一大段文本的地方,例如脚本的help函数。

    19.删除字符串中的第一个或者最后一个字符
    假设字符串为:

    [Sun Nov 04 10:21 AM] [kodango@devops] ~/workspace 
    $ str="aremoveb"
    

    可能你第一个想法是通过sed或者其它命令来完成这个功能,但是其实有很简单的方法:

    [Sun Nov 04 10:24 AM] [kodango@devops] ~/workspace 
    $ echo "${str#?}"
    removeb
    
    [Sun Nov 04 10:24 AM] [kodango@devops] ~/workspace 
    $ echo "${str%?}"
    aremove
    

    类似地,你也可以删除2个、3个、4个……

    有没有一次性删除第一个和最后一个字符的方法呢?答案当然是肯定的:

    [Sun Nov 04 10:26 AM] [kodango@devops] ~/workspace 
    $ echo "${str:1:-1}"
    remove
    

    关于这些变量替换的内容在bash的man手册中都有说明。

    20. 使用逗号join数组元素
    假设数组元素没有空格,可以用这种方法:

    [Sun Nov 04 10:14 AM] [kodango@devops] ~/workspace 
    $ a=(1 2 3) 
    $ b="${a[*]}"
    
    [Sun Nov 04 10:15 AM] [kodango@devops] ~/workspace 
    $ echo ${b// /,}
    1,2,3
    

    注意:当该数组的长度非常长时,使用这种替换的时间开销很高,性能很差,推荐用sed。
    假设数组元素包含有空格,可以借用printf命令来达到:

    [Sun Nov 04 10:15 AM] [kodango@devops] ~/workspace 
    $ a=(1 "2 3" 4)
    
    [Sun Nov 04 10:15 AM] [kodango@devops] ~/workspace 
    $ printf ",%s" "${a[@]}" | cut -c2-   
    1,2 3,4
    

    21. Shell中的多进程
    在命令行下,我们会在命令行后面加上&符号来让该命令在后台执行,在shell脚本中,使用”(cmd)”可以让fork一个子shell来执行该命令。利用这两点,可以实现shell的多线程:

    job_num=10
    
    function do_work()
    {
        echo "Do work.."
    }
    
    for ((i=0; i < job_num ;i++)); do
        echo "Fork job $i"
        (do_work) &
    done
    
    wait   # wait for all job done
    echo "All job have been done!"
    

    注意最后的wait命令,作用是等待所有子进程结束。
    附几则小技巧:

    1)sudo iptables -L -n | vim -
    2)grep -v xxx | vim -
    3)echo $'\''
    4)set -- 1 2 3; echo "$@"
    5)搜索stackoverflow/superuser等站点
    6)VIM编辑远程文件 vim scp://xxx//etc/vimrc
    7)远程执行脚本 ssh xxx bash < xxx.sh
    

    来源:http://kodango.com/simple-bash-programming-skills

    解决putty network error software caused connection abort 自动中断

    最近使用putty的时候老是提示 network error software caused connection abort 网络错误软件造成连接中断

    下面给出解决putty连接中断办法:

    1.在linux服务器端设置:

    #vim /etc/ssh/sshd_config
    

    找到 TCPKeepAlive yes把前面的#去掉

    找到ClientAliveInterval 参数去掉前面的#

    ClientAliveInterval 60 把后面的0改成60

    ClientAliveInterval指定了服务器端向客户端请求消息的时间间隔, 默认是0,不发送。而ClientAliveInterval 60表示每分钟发送一次,然后客户端响应,这样就保持长连接了。这里比较怪的地方是:不是客户端主动发起保持连接的请求(如FTerm, CTerm等),而是需要服务器先主动。

    另外,至于ClientAliveCountMax,使用默认值3即可。ClientAliveCountMax表示服务器发出请求后客户端没有响应的次数达到一定值,就自动断开,正常情况下,客户端不会不响应。

    最后记得重启sshd,使所有更改生效:

    #systemctl restart sshd
    

    2.putty客户端设置:

    在 Connection 里面有个 Seconds between keepaliaves,这里就是每间隔指定的秒数,就给服务器发送一个空的数据包,来保持连接。以免登录的主机那边在长时间没接到数据后,会自动断开 SSH 的连接
    1
    上图中,默认输入 0 是禁用保持连接,在这里我习惯的设置了 60。两个复选框都保持默认选中。

    下一步保存session这一步很重要哦 在session 设置 host name 、saved sessions 点击save按钮 ,如下图设置:
    2

    来源:http://www.jsjtt.com/xitongyingyong/linux/45.html

    centos7 firewalld下安装pptpd

    1、安装pptpd

    rpm -Uvh http://download.fedoraproject.org/pub/epel/beta/7/x86_64/epel-release-7-1.noarch.rpm
    yum -y install ppp pptpd
    

    2、编辑pptpd.conf文件,添加下面2行

    #vim /etc/pptpd.conf
    localip 10.10.0.1
    remoteip 10.10.0.100-199
    

    3、编辑options.pptpd文件,添加google dns至文件内

    #vim /etc/ppp/options.pptpd
    ms-dns 8.8.8.8
    ms-dns 8.8.4.4
    

    4、编辑chap-secrets文件,添加用户密码,将USERNAME和PASSWORD换成你的用户名和密码

    #vim /etc/ppp/chap-secrets
    USERNAME pptpd PASSWORD *
    

    5、编辑sysctl.conf文件,为pptpd添加转发规则

    #vim /etc/sysctl.conf
    net.ipv4.ip_forward = 1
    

    然后执行

    #sysctl -p
    

    6、将pptpd添加至firewalld规则,并开机启动
    添加新的firewalld服务:

    #firewall-cmd --permanent --new-service=pptp
    

    建立并配置pptp服务规则、类型、端口:

    #vim /etc/firewalld/services/pptp.xml
    <?xml version="1.0" encoding="utf-8"?>
    <service>
      <port protocol="tcp" port="1723"/>
    </service>
    

    7、将pptp服务添加至firewall,并使其生效

    #firewall-cmd --permanent --zone=public --add-service=pptp
    #firewall-cmd --permanent --zone=public --add-masquerade
    #firewall-cmd --reload
    

    8、启动pptp服务

    #systemctl start pptpd
    #systemctl enable pptpd.service