shell入门

这篇文章主要是网上搜集整理的资料,在shell的在平时使用中需要的最基本的入门知识,没有涉及grep、sed、awk以及各种正则表达式。

hello world

#!/bin/bash 
echo Hello World

脚本写完之后在shell中运行

$ chmod u+x hello
$ ./hello

就可以看到结果了。

输入输出

在 Linux 系统中:标准输入(stdin)默认为键盘输入;标准输出(stdout)默认为屏幕输出;标准错误输出(stderr)默认也是输出到屏幕(上面的 std 表示 standard)。

在 BASH 中使用这些概念时一般将标准输出表示为 1,将标准错误输出表示为 2。

$ ls > ls_result			# 写入
$ ls -l >> ls_result		# 追加写入
$ find /home -name lost* 2> err_result 	# 重定向错误输出到文件
$ find /home -name lost* > all_result 2>& 1	# 标准输出连同错误输出重定向到文件
$ find /home -name lost* 2> /dev/null	# 不显示错误信息

变量

  • 变量赋值时,’=’左右两边都不能有空格;
  • BASH 中的语句结尾不需要分号(”;”);
  • 除了在变量赋值和在FOR循环语句头中,BASH 中的变量使用必须在变量前加”$”符号,可一个使用更为标准的变量引用方式${STR}

BASH 中的变量既不需要定义,也就没有类型一说,一个变量即可以被定义为一个字符串,也可以被再定义为整数。如果对该变量进行整数运算,他就被解释为整数;如果对他进行字符串操作,他就被看作为一个字符串。

整数运算一般通过 let 和 expr 这两个指令来实现,如对变量 x 加 1 可以写作:let "x = $x + 1" 或者 x=expr $x + 1

比较操作符

整数变量和字符串变量

对应的操作 整数操作 字符串操作
相同 -eq =
不同 -ne !=
大于 -gt >
小于 -lt <
大于或等于 -ge  
小于或等于 -le  
为空 -z  
不为空 -n  

比较字符串 a 和 b 是否相等就写作:if [ $a = $b ]
判断字符串 a 是否为空就写作: if [ -z $a ]
判断整数变量 a 是否大于 b 就写作:if [ $a -gt $b ]
更细致的文档推荐在字符串比较时尽量不要使用 -n ,而用 ! -z 来代替。

判断文件属性

运算符 含义( 满足下面要求时返回 TRUE )
-e file 文件 file 已经存在
-f file 文件 file 是普通文件
-s file 文件 file 大小不为零
-d file 文件 file 是一个目录
-r file 文件 file 对当前用户可以读取
-w file 文件 file 对当前用户可以写入
-x file 文件 file 对当前用户可以执行
-g file 文件 file 的 GID 标志被设置
-u file 文件 file 的 UID 标志被设置
-O file 文件 file 是属于当前用户的
-G file 文件 file 的组 ID 和当前用户相同
file1 -nt file2 文件 file1 比 file2 更新
file1 -ot file2 文件 file1 比 file2 更老

if [ -x /root ] 可以用于判断 /root 目录是否可以被当前用户进入。

基本流程控制

if…then…else

if 条件语句 [ ] 左右两个都要有一个空格。

if [ $1 -gt 90 ] 
then 
    echo "Good, $1" 
elif [ $1 -gt 70 ] && [ $1 -lt 90]
then 
    echo "OK, $1" 
else 
    echo "Bad, $1" 
fi

当shell提醒integer expression expected时,可使用字符串比较,使得程序得以正确地运行通过。例如
if [ $cpuLoad > '50' ] || [ $memUsed > '800' ] || [ $ioSumRate > '25' ];

for

for day in Sun Mon Tue Wed Thu Fri Sat 
do 
	echo $day 
done 

while

while & until

while [ condition ]
do
	statments
done

until [ condition is TRUE ]
do
	statments
done

case

echo "Hit a key, then hit return." 
read Keypress 

case "$Keypress" in 
[a-z] ) echo "Lowercase letter";; 
[A-Z] ) echo "Uppercase letter";; 
[0-9] ) echo "Digit";; 
* ) echo "Punctuation, whitespace, or other";; 
esac 

函数

square() { 
	let "res = $1 * $1" 
	return $res 
} 

特殊保留字

保留变量

$IFS  这个变量中保存了用于分割输入参数的分割字符,默认识空格。 
$HOME  这个变量中存储了当前用户的根目录路径。 
$PATH  这个变量中存储了当前 Shell 的默认路径字符串。 
$PS1  表示第一个系统提示符。 
$PS2  表示的二个系统提示符。 
$PWD  表示当前工作路径。 
$EDITOR 表示系统的默认编辑器名称。 
$BASH  表示当前 Shell 的路径字符串。
$0, $1, $2, ... 
表示系统传给脚本程序或脚本程序传给函数的第0个、第一个、第二个等参数。
$#   表示脚本程序的命令参数个数或函数的参数个数。
$$   表示该脚本程序的进程号,常用于生成文件名唯一的临时文件。 
$?   表示脚本程序或函数的返回状态值,正常为 0,否则为非零的错误号。
$*   表示所有的脚本参数或函数参数。
$@   和 $* 涵义相似,但是比 $* 更安全。
$!   表示最近一个在后台运行的进程的进程号。
$RANDOM 一个大小在 1 到 65536 之间的随机整数

符号

算术运算符 
	+ - * / % 表示加减乘除和取余运算
	+= -= *= /= 同 C 语言中的含义
位操作符
	<< <<= >> >>= 表示位左右移一位操作
	& &= | |= 表示按位与、位或操作
	~ ! 表示非操作
	^ ^= 表示异或操作
关系运算符 
	< > <= >= == != 表示大于、小于、大于等于、小于等于、等于、不等于操作
	&& || 逻辑与、逻辑或操作

变量的特殊操作

${var-default} 表示如果变量 $var 还没有设置,则保持 $var 没有设置的状态,并返回后面的默认值 default。
${var=default} 表示如果变量 $var 还没有设置,则取后面的默认值 default。 
${var+otherwise} 表示如果变量 $var 已经设置,则返回 otherwise 的值,否则返回空( null )。
${var?err_msg} 表示如果变量 $var 已经设置,则返回该变量的值,否则将后面的 err_msg 输出到标准错误输出上。

还有下面一些用法,这些用法主要用于从文件路径字符串中提取有用信息:

${var#pattern}, ${var##pattern} 用于从变量 $var 中剥去最短(最长)的和 pattern 相匹配的最左侧的串。
${var%pattern}, ${var%%pattern} 用于从变量 $var 中剥去最短(最长)的和 pattern 相匹配的最右侧的串。
${var:pos} 表示去掉变量 $var 中前 pos 个字符。
${var:pos:len} 表示变量 $var 中去掉前 pos 个字符后的剩余字符串的前 len 个字符。
${var/pattern/replacement} 表示将变量 $var 中第一个出现的 pattern 模式替换为 replacement 字符串。
${var//pattern/replacement} 表示将变量 $var 中出现的所有 pattern 模式全部都替换为 replacment 字符串。

程序界面

BASH 中提供了一个小的语句格式,可以让程序快速的设计出一个字符界面的用户交互选择的菜单,该功能就是由 select 语句来实现的。

OPTIONS="Hello Quit" 
select opt in $OPTIONS; do 
if [ "$opt" = "Quit" ]; then 
echo done 
exit 
elif [ "$opt" = "Hello" ]; then 
echo Hello World 
else 
clear 
echo bad option 
fi 
done 

BASH 中通过 read 函数来实现读取用户输入的功能

echo Please enter your name
read NAME 
echo "Hi! $NAME !"

也可以通过文本输入。要求在需要键盘输入的命令后,直接加上 « 符号,然后跟上一个自定义的字符串,在该串后按顺序输入本来应该由键盘输入的所有字符,在所有需要输入的字符都结束后,重复一遍自定义的字符串,表示该输入到此结束。

passwd="aka@tsinghua" 
ftp -n localhost <<(U •́ .̫ •̀ U)
user anonymous $passwd
(U •́ .̫ •̀ U)

一些特殊的惯用法

  • 在 BASH 中 () 一对括号一般被用于求取括号中表达式的值或命令的执行结果,如:(a=hello; echo $a) ,其作用相当于 ...

  • : 有两个含义,一是表示空语句,有点类似于 C 语言中的单个 “;” 。表示该行是一个空命令,如果被用在 while/until 的头结构中,则表示值 0,会使循环一直进行下去

      while : 
      do 
      operation
      done
    
  • : 还可以用于求取后面变量的值

      : ${HOSTNAME?} {USER?} {MAIL?} 
      echo $HOSTNAME 
      echo $USER 
      echo $MAIL 
    
  • 在 BASH 中 export 命令用于将系统变量输出到外层的 Shell 中。
  • bash -x bash-script 命令,可以查看一个出错的 BASH 脚本到底错在什么地方,可以帮助程序员找出脚本中的错误。
  • trap 语句可以在 BASH 脚本出错退出时打印出一些变量的值,以供程序员检查。trap 语句必须作为继 “#!/bin/bash” 后的第一句非注释代码,一般 trap 命令被写作: trap ‘message $checkvar1 $checkvar2’ EXIT 。

Linux命令之netstat命令和一些 TCP 知识

在Internet RFC标准中,Netstat的定义是: Netstat是在内核中访问网络及相关信息的程序,它能提供TCP连接,TCP和UDP监听,进程内存管理的相关报告。 Netstat是是一个监控TCP/IP网络的非常有用的工具,它可以显示路由表、实际的网络连接以及每一个网络接口设备的状态信息。Netstat用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。

格式

该命令的一般格式为 :
netstat [-a][-e][-n][-o][-p Protocol][-r][-s][Interval]
命令中各选项的含义如下:
  
-a 显示所有socket,包括正在监听的。
-c 每隔1秒就重新显示一遍,直到用户中断它。
-i 显示所有网络接口的信息,再加上-e,内容便和ifconfig一样。
-n 禁用反向域名解析,加快查询速度
-r 显示核心路由表,格式同“route -e”。
-t 显示TCP协议的连接情况
-u 显示UDP协议的连接情况。
-v 显示正在进行的工作。
-l 只列出监听中的连接.(LISTEN)
-p 选项查看进程信息
-g 会输出 IPv4 和 IPv6 的多播组信息。

常用选项

netstat -s
	——本选项能够按照各个协议分别显示其统计数据。如果你的应用程序(如Web浏览器)运行速度比较慢,或者不能显示Web页之类的数据,那么你就可以用本选项来查看一下所显示的信息。你需要仔细查看统计数据的各行,找到出错的关键字,进而确定问题所在。
netstat -e
	——本选项用于显示关于以太网的统计数据,它列出的项目包括传送数据报的总字节数、错误数、删除数,包括发送和接收量(如发送和接收的字节数、数据包数),或有广播的数量。可以用来统计一些基本的网络流量。
netstat -r
	——本选项可以显示关于路由表的信息,类似于后面所讲使用route print命令时看到的信息。除了显示有效路由外,还显示当前有效的连接。
netstat -a
	——本选项显示一个所有的有效连接信息列表,包括已建立的连接(ESTABLISHED),也包括监听连接请求(LISTENING)的那些连接。
netstat -n
	——显示所有已建立的有效连接。
netstat -nlpt
	——查看端口和连接的信息时,能查看到它们对应的进程名和进程号对系统管理员来说是非常有帮助的。举个栗子,Apache 的 httpd 服务开启80端口,如果你要查看 http 服务是否已经启动,或者 http 服务是由 apache 还是 nginx 启动的,这时候你可以看看进程名。
netstat -ltpe
	——使用 -ep 选项可以同时查看进程名和用户名。
netstat -ant
	——查看已建立的tcp端口情况。

一些其它TCP的知识

TCP连接

先上一下客户端与服务器进行TCP连接的过程。TCP连接的的过程有很多状态,不同的连接状态,都有想对应的状态码:

LISTEN:侦听来自远方的TCP端口的连接请求
SYN-SENT:再发送连接请求后等待匹配的连接请求
SYN-RECEIVED:再收到和发送一个连接请求后等待对方对连接请求的确认
ESTABLISHED:代表一个打开的连接
FIN-WAIT-1:等待远程TCP连接中断请求,或先前的连接中断请求的确认
FIN-WAIT-2:从远程TCP等待连接中断请求
CLOSE-WAIT:等待从本地用户发来的连接中断请求
CLOSING:等待远程TCP对连接中断的确认
LAST-ACK:等待原来的发向远程TCP的连接中断请求的确认
TIME-WAIT:等待足够的时间以确保远程TCP接收到连接中断请求的确认
CLOSED:没有任何连接状态

相对应的就是下图: vim

连接状态

在原模式中没有状态,在用户数据报协议中也经常没有状态,于是状态列可以空出来。若有状态,可以对应TCP的三次握手四次挥手过程,通常取值为:

LISTEN
	侦听来自远方的TCP端口的连接请求
SYN-SENT
	再发送连接请求后等待匹配的连接请求
SYN-RECEIVED
	再收到和发送一个连接请求后等待对方对连接请求的确认
ESTABLISHED
	代表一个打开的连接 
FIN-WAIT-1
	等待远程TCP连接中断请求,或先前的连接中断请求的确认
FIN-WAIT-2
	从远程TCP等待连接中断请求
CLOSE-WAIT
	等待从本地用户发来的连接中断请求
CLOSING
	等待远程TCP对连接中断的确认
LAST-ACK
	等待原来的发向远程TCP的连接中断请求的确认
TIME-WAIT
	等待足够的时间以确保远程TCP接收到连接中断请求的确认
CLOSED
	没有任何连接状态

基于三次握手的SYN洪水攻击(摘自百度百科)

建设一个小型的模仿环境假设有3台接入互联网的机器。A为攻击者操纵的攻击机。B为中介跳板机器(受信任的服务器)。C为受害者使用的机器(多是服务器),这里把C机器锁定为目标机器。A机器向B机器发送SYN包,请求建立连接,这时已经响应请求的B机器会向A机器回应SYN/ACK表明同意建立连接,当A机器接受到B机器发送的SYN/ACK回应时,发送应答ACK建立A机器与B机器的网络连接。这样一个两台机器之间的TCP通话信道就建立成功了。 B终端受信任的服务器向C机器发起TCP连接,A机器对服务器C发起SYN信息,使C机器不能响应B机器。在同时A机器也向B机器发送虚假的C机器回应的SYN数据包,接收到SYN数据包的B机器(被C机器信任)开始发送应答连接建立的SYN/ACK数据包,这时C机器正在忙于响应以前发送的SYN数据而无暇回应B机器,A机器的攻击者预测出B机器包的序列号(TCP序列号预测难度有些大)假冒C机器向B机器发送应答ACK这时攻击者骗取B机器的信任,假冒C机器与B机器建立起TCP协议的对话连接。这个时候的C机器还是在响应攻击者A机器发送的SYN数据。

TCP协议栈的弱点

TCP连接的资源消耗,其中包括:数据包信息、条件状态、序列号等。通过故意不完成建立连接所需要的三次握手过程,造成连接一方的资源耗尽。 通过攻击者有意的不完成建立连接所需要的三次握手的全过程,从而造成了C机器的资源耗尽。序列号的可预测性,目标主机应答连接请求时返回的SYN/ACK的序列号时可预测的。


debian登陆信息修改

一般我们ssh登陆debian会出现以下的信息。

Linux kelu.org 3.18.1-x86_64-linode50 #1 SMP Tue Jan 6 12:14:10 EST 2015 x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Jan 15 23:16:37 2015 from xxx.xxx.xxx.xxx

修改和添加的方法很多。在此记录一下。

登陆前提示信息文件是 /etc/issue 和 /etc/issue.net. 登陆前的信息我们不管了,就来看登陆后的信息好了。

##start######## /etc/update-motd.d/10-uname #######################
Linux kelu.org 3.18.1-x86_64-linode50 #1 SMP Tue Jan 6 12:14:10 EST 2015 x86_64
##end###############################################

##start######## 以下是/etc/motd #######################
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
##end###############################################

##start######## 以下是/etc/ssh/sshd_config 的 PrintLastLog 配置######
###################### PrintLastLog ##################
Last login: Thu Jan 15 23:16:37 2015 from xxx.xxx.xxx.xxx
##end###############################################

motd的意思是message of the day——每日消息

接下来是

/etc/ssh/sshrc ssh登陆之后会加载里面的内容。

/etc/bash.bashrc 值得注意的是,screen时候也会加载这个文件。

在/etc中的配置文件,会对所有用户生效。如果只希望对当前用户做自定义的配置,就在用户目录下进行配置。

我最后改成了这样:

image-20230905152603077

/etc/update-motd.d/10-uname 文件:

echo "YUKI.N > SELECT シリアルコード \n         FROM データべース \n         WHERE コードデータ \n         ORDER BY 攻性情報戦闘 \n         HAVING ターミネートモ
ード"

/etc/motd文件:


YUKI.N > If you are reading this,
         then I'm probably no longer myself

YUKI.N > When this message apprears,
         it means that you, me, Suzumiya Haruhi, Asahina Mikuru and
         Koizumi Itsuki are all present.

YUKI.N > That's the key.

YUKI.N > This is ermegency escape program.
         To activate it, press enter.
         Otherwise press any other key.

         If you activate it,
         you will be a given time to repair space-time continuum.
         However there's no guarantee of your safe return.

YUKI.N > This program can be start only once.
         Once execute it will be erased.

         If you choose not to activate it, it will also be erased.
         READY?


备份你的Linux ——tar 打包和 rsync 同步

做好系统备份对系统管理员来说是件很重要的事情。可使用两种方法进行备份系统。一种是直接打tar包备份,另一种是使用增量备份工具,下面我来记录一下。

1. tar打包备份

tar打包备份很简单,就是一条tar命令。为了增强备份文件的可读性,我们将备份的时间设置为备份文件名。

#!/bin/bash

function bksys() {
    filename=`date --date="-24 hour" +%Y-%m-%d_%H-%M`;
    tar cvpzf /kelu/Backup/$filename.tar.gz --exclude=/proc --exclude=/tmp --exclude=/lost+found --exclude=/mnt --exclude=/sys --exclude=/kelu/Backup/ --exclude=/pub /;
}

bksys 2>&1 | tee -a /var/log/bksys.log

其中tar的-p的意思在man中的解释是: -p 恢复字段到它们的原始方式,忽略现有的用户权限屏蔽位(umask)。 setuid、setgid 和 tacky 位许可权也恢复给拥有 kelu 用户权限的用户。这个标志恢复文件到其原始方式,但不恢复目录到其原始方式。

意思也就是说打包时保持该文件夹的相关属性,使解压的时候得以恢复。

2. rsync备份

rsync 是一个快速增量文件传输工具,它可以用于在同一主机备份内部的备份,我们还可以把它作为不同主机网络备份工具之用。本文主要讲述的是如何自架rsync服务器,以实现文件传输、备份和镜像。相对tar和wget来说,rsync 也有其自身的优点,比如速度快、安全、高效。

安装

debian安装使用apt-get install rsync安装。有的是系统自带的,自带的话就自己建好文件夹/etc/rsyncd,在文件夹里添加几个文件rsyncd.motd rsyncd.password rsyncd.secrets。 这三个文件的内容分别是:

YUKI.N> cat rsyncd.motd
+++++++++++++++++++++++++++
+     kelu.org  2015      +
+++++++++++++++++++++++++++
YUKI.N> cat rsyncd.password
12345678
YUKI.N> cat rsyncd.secrets
kelu:12345678

secrets和password的权限必须设为600,不然备份时候也会提醒也会拒绝备份= = secrets是用户密码文件,password是为了方便自动化备份时的密码文件。交互式地备份的话会提醒你输入密码。

配置

配置rsyncd.conf

# Distributed under the terms of the GNU General Public License v2
# Minimal configuration file for rsync daemon
# See rsync(1) and rsyncd.conf(5) man pages for help

# This line is required by the /etc/init.d/rsyncd script
pid file = /var/run/rsyncd.pid
port = 873
# 你的IP地址!
address = 12.34.56.78 
#uid = nobody
#gid = nobody
uid = kelu
gid = kelu

use chkelu = yes
read only = yes

#limit access to private LANs 注意要把自己的IP添加进去!
hosts allow=192.168.1.0/255.255.255.0 10.0.1.0/255.255.255.0 
hosts deny=*

max connections = 5
# motd文件,欢迎语来着,在里面随便写点东西。当用户登录时会看到这个信息。
motd file = /etc/rsyncd/rsyncd.motd

#This will give you a separate log file
log file = /var/log/rsync.log

#This will log every file transferred - up to 85,000+ per user, per sync
transfer logging = yes

log format = %t %a %m %f %b
syslog facility = local3
timeout = 300

# 模块定义啦
[模块名称]
path = /
list=yes
ignore errors
auth users = kelu
secrets file = /etc/rsyncd/rsyncd.secrets
comment = YUKI.N>
exclude = proc/ tmp/ lost+found/ mnt/ sys/ kelu/Backup/ pub/

​ 做完这些,已经可以开始同步数据了。由于是本机备份,所以我没有看得很仔细,以后需要了再来看啦。

/usr/bin/rsync --daemon 				# 启动服务
rsync --list-only kelu@kelu.org:: 		# 备份信息
rsync -avzP kelu@kelu.org::kelu.org /kelu/Dropbox/kelu.org/
										# 备份

写一个脚本,方便自动化。要记得chmod +x哦

#!/bin/sh
# /usr/bin/rsync --daemon;
# 全量备份
# rsync -avzP --password-file=/etc/rsyncd/rsyncd.secrets kelu@kelu.org::kelu.org /kelu/Dropbox/kelu.org/$(date + '%m-%d-%y')
rsync -avzP --password-file=/etc/rsyncd/rsyncd.password kelu@kelu.org::kelu.org /kelu/Dropbox/kelu.org/											

3. 自动化

crontab -e

按照提示添加就好了。比如:

# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.

# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/

40 2 * * * rsync -avzP --password-file=/etc/rsyncd/rsyncd.password kelu@kelu.org::kelu.org /kelu/Dropbox/kelu.org/

或者你也可以按照系统的方法,新建一个自动化运行的文件夹,定时运行文件夹中的文件。

10 4 * * * /usr/bin/run-parts   /etc/cron.daily.kelu    1> /dev/null

在你的Debian上连接到Github

整个过程都是参考的github的官方文档。在这稍微做一个记录。

$ apt-get install git
$ git config --global user.name "YOUR NAME"
$ git config --global user.email "YOUR EMAIL ADDRESS"
$ ssh-keygen -t rsa -C "YOUR EMAIL ADDRESS"

将你的公钥添加到github上。

$ ssh-agent bash
$ ssh-agent -s
$ ssh-add ~/.ssh/id_rsa
$ ssh -T git@github.com

出现以下这行就说明连接上了。

Hi username! You've successfully authenticated, but GitHub does not # provide shell access.

把自己的项目下载到本地,就可以任意编辑了。

$ git clone git@github.com:YOUR NAME/PROJECT.github.com.git

安装完成。


写出健壮的 Bash 脚本

原文来自 开源中国社区

许多人用shell脚本完成一些简单任务,而且变成了他们生命的一部分。不幸的是,shell脚本在运行异常时会受到非常大的影响。在写脚本时将这类问题最小化是十分必要的。本文中我将介绍一些让bash脚本变得健壮的技术。

使用set -u

你因为没有对变量初始化而使脚本崩溃过多少次?对于我来说,很多次。

chroot=$1
...
rm -rf $chroot/usr/share/doc

如果上面的代码你没有给参数就运行,你不会仅仅删除掉chroot中的文档,而是将系统的所有文档都删除。那你应该做些什么呢?好在bash提供了set -u,当你使用未初始化的变量时,让bash自动退出。你也可以使用可读性更强一点的set -o nounset。

david% bash /tmp/shrink-chroot.sh
$chroot=
david% bash -u /tmp/shrink-chroot.sh
/tmp/shrink-chroot.sh: line 3: $1: unbound variable
david%

使用set -e

你写的每一个脚本的开始都应该包含set -e。这告诉bash一但有任何一个语句返回非真的值,则退出bash。使用-e的好处是避免错误滚雪球般的变成严重错误,能尽早的捕获错误。更加可读的版本:set -o errexit 使用-e把你从检查错误中解放出来。如果你忘记了检查,bash会替你做这件事。不过你也没有办法使用$?来获取命令执行状态了,因为bash无法获得任何非0的返回值。你可以使用另一种结构:

command
if [ "$?"-ne 0]; then echo "command failed"; exit 1; fi 可以替换成:

command || { echo "command failed"; exit 1; }

或者使用:

if ! command; then echo "command failed"; exit 1; fi
如果你必须使用返回非0值的命令,或者你对返回值并不感兴趣呢?你可以使用 command   true ,或者你有一段很长的代码,你可以暂时关闭错误检查功能,不过我建议你谨慎使用。
set +e
command1
command2
set -e
相关文档指出,bash默认返回管道中最后一个命令的值,也许是你不想要的那个。比如执行 false true 将会被认为命令成功执行。如果你想让这样的命令被认为是执行失败,可以使用 set -o pipefail

##程序防御 - 考虑意料之外的事

你的脚本也许会被放到“意外”的账户下运行,像缺少文件或者目录没有被创建等情况。你可以做一些预防这些错误事情。比如,当你创建一个目录后,如果父目录不存在,mkdir 命令会返回一个错误。如果你创建目录时给mkdir命令加上-p选项,它会在创建需要的目录前,把需要的父目录创建出来。另一个例子是 rm 命令。如果你要删除一个不存在的文件,它会“吐槽”并且你的脚本会停止工作。(因为你使用了-e选项,对吧?)你可以使用-f选项来解决这个问题,在文件不存在的时候让脚本继续工作。 准备好处理文件名中的空格 有些人从在文件名或者命令行参数中使用空格,你需要在编写脚本时时刻记得这件事。你需要时刻记得用引号包围变量。

if [ $filename = "foo" ];

当$filename变量包含空格时就会挂掉。可以这样解决:

if [ "$filename" = "foo" ];

使用$@变量时,你也需要使用引号,因为空格隔开的两个参数会被解释成两个独立的部分。

david% foo() { for i in $@; do echo $i; done }; foo bar "baz quux"
bar
baz
quux
david% foo() { for i in "$@"; do echo $i; done }; foo bar "baz quux"
bar
baz quux

我没有想到任何不能使用”$@”的时候,所以当你有疑问的时候,使用引号就没有错误。 如果你同时使用find和xargs,你应该使用 -print0 来让字符分割文件名,而不是换行符分割。

david% touch "foo bar"
david% find | xargs ls
ls: ./foo: No such file or directory
ls: bar: No such file or directory
david% find -print0 | xargs -0 ls
./foo bar

##设置的陷阱

当你编写的脚本挂掉后,文件系统处于未知状态。比如锁文件状态、临时文件状态或者更新了一个文件后在更新下一个文件前挂掉。如果你能解决这些问题,无论是 删除锁文件,又或者在脚本遇到问题时回滚到已知状态,你都是非常棒的。幸运的是,bash提供了一种方法,当bash接收到一个UNIX信号时,运行一个 命令或者一个函数。可以使用trap命令。

trap command signal [signal ...]

你可以链接多个信号(列表可以使用kill -l获得),但是为了清理残局,我们只使用其中的三个:INT,TERM和EXIT。你可以使用-as来让traps恢复到初始状态。 信号描述

INT Interrupt - 当有人使用Ctrl-C终止脚本时被触发 TERM Terminate - 当有人使用kill杀死脚本进程时被触发 EXIT Exit - 这是一个伪信号,当脚本正常退出或者set -e后因为出错而退出时被触发

当你使用锁文件时,可以这样写:

if [ ! -e $lockfile ]; then
touch $lockfile
critical-section
rm $lockfile
else
echo "critical-section is already running"

fi 当最重要的部分(critical-section)正在运行时,如果杀死了脚本进程,会发生什么呢?锁文件会被扔在那,而且你的脚本在它被删除以前再也不会运行了。解决方法:

if [ ! -e $lockfile ]; then
trap " rm -f $lockfile; exit" INT TERM EXIT
touch $lockfile
critical-section
rm $lockfile
trap - INT TERM EXIT
else
echo "critical-section is already running"
fi

现在当你杀死进程时,锁文件一同被删除。注意在trap命令中明确地退出了脚本,否则脚本会继续执行trap后面的命令。 竟态条件 (wikipedia) 在上面锁文件的例子中,有一个竟态条件是不得不指出的,它存在于判断锁文件和创建锁文件之间。一个可行的解决方法是使用IO重定向和bash的noclobber(wikipedia)模式,重定向到不存在的文件。我们可以这么做:

if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null;
then
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
critical-section
rm -f "$lockfile"
trap - INT TERM EXIT
else
echo "Failed to acquire lockfile: $lockfile"
echo "held by $(cat $lockfile)"
fi

更复杂一点儿的问题是你要更新一大堆文件,当它们更新过程中出现问题时,你是否能让脚本挂得更加优雅一些。你想确认那些正确更新了,哪些根本没有变化。比如你需要一个添加用户的脚本。

add_to_passwd $user
cp -a /etc/skel /home/$user
chown $user /home/$user -R

当磁盘空间不足或者进程中途被杀死,这个脚本就会出现问题。在这种情况下,你也许希望用户账户不存在,而且他的文件也应该被删除。

rollback() {
    del_from_passwd $user
        if [ -e /home/$user ]; then
            rm -rf /home/$user
        fi
        exit
}
trap rollback INT TERM EXIT
add_to_passwd $user
cp -a /etc/skel /home/$user
chown $user /home/$user -R
trap - INT TERM EXIT

在脚本最后需要使用trap关闭rollback调用,否则当脚本正常退出的时候rollback将会被调用,那么脚本等于什么都没做。

##保持原子化

又是你需要一次更新目录中的一大堆文件,比如你需要将URL重写到另一个网站的域名。你也许会写:

for file in $(find /var/www -type f -name "*.html"); do
perl -pi -e 's/www.example.net/www.example.com/' $file
done

如果修改到一半是脚本出现问题,一部分使用www.example.com,而另一部分使用www.example.net。你可以使用备份和trap解决,但在升级过程中你的网站URL是不一致的。 解决方法是将这个改变做成一个原子操作。先对数据做一个副本,在副本中更新URL,再用副本替换掉现在工作的版本。你需要确认副本和工作版本目录在同一个磁盘分区上,这样你就可以利用Linux系统的优势,它移动目录仅仅是更新目录指向的inode节点。

cp -a /var/www /var/www-tmp
for file in $(find /var/www-tmp -type -f -name "*.html"); do
perl -pi -e 's/www.example.net/www.example.com/' $file
done
mv /var/www /var/www-old
mv /var/www-tmp /var/www

这意味着如果更新过程出问题,线上系统不会受影响。线上系统受影响的时间降低为两次mv操作的时间,这个时间非常短,因为文件系统仅更新inode而不用真正的复制所有的数据。

这种技术的缺点是你需要两倍的磁盘空间,而且那些长时间打开文件的进程需要比较长的时间才能升级到新文件版本,建议更新完成后重新启动这些进程。对于 apache服务器来说这不是问题,因为它每次都重新打开文件。你可以使用lsof命令查看当前正打开的文件。优势是你有了一个先前的备份,当你需要还原 时,它就派上用场了。


vim的简略配置

曾经用过比较长一段时间的vim,因为后来一直在windows下开发,也就没怎么用过了。现在平台移到了Linux和Mac,Mac还好各种工具,前段时间试用了Coda,觉得真的挺好用的。虽然如此,在Linux下开发就显得很捉急了,因为都是终端进去敲的。虽然也可以在本地写好了再上传,终究麻烦。而且更重要的一点是——Coda太贵,用不起Orz。

vim

于是,重操旧业,今晚把vim好好整理了一下,把以前最基本的配置拿了回来。装上了几个简单的插件,就这么先用着了。

备注都给的很详细了,就不说了。就装了两个插件 supertab和neocomplcache(我真的对自动补全怨念很深啊233333333)其实只要最基本的补全搞定了,其他的不爽也少了很多。

插件的使用方法就是把 vim_config.tgz 在本地用户目录解压就好了。这个配置文件存在用户目录下命名为 .vimrc

	" ==========通用设置start================
	set encoding=utf-8
	set fileencodings=utf-8,shift-jis,cp936,latin1
	colo django " 主题配色
	set nocompatible " 关闭VI兼容模式
	set expandtab " 将tab扩展为空
	set mouse=a  " 开启鼠标模式
	set guifont=Monaco:h18 "字体
	set shiftwidth=4			" 设定 << 和 >> 命令移动时的宽度为 4
	set softtabstop=4		   " 使得按退格键时可以一次删掉 4 个空格
	set tabstop=4			   " 设定 tab 长度为 4
	set nobackup				" 覆盖文件时不备份
	set autochdir			   " 自动切换当前目录为当前文件所在的目录
	set backupcopy=yes		  " 设置备份时的行为为覆盖
	set ignorecase smartcase	" 搜索时忽略大小写,但在有一个或以上大写字母时仍大小写敏感
	set nowrapscan			  " 禁止在搜索到文件两端时重新搜索
	set incsearch			   " 输入搜索内容时就显示搜索结果
	set showmatch			   " 插入括号时,短暂地跳转到匹配的对应括号
	set matchtime=2			 " 短暂跳转到匹配括号的时间
	set magic				   " 显示括号配对情况
	set hidden				  " 允许在有未保存的修改时切换缓冲区,此时的修改由 vim 负责保存
	set nu " 显示行号
	set autochdir "自动切换工作目录
	syntax on " 语法高亮
	set so=4 " 设置光标距离上下边界的距离
	set ruler " 开启右下角光标位置显示
	set showcmd " 在窗口右下角显示完整命令已输入部分
	set cursorline " 高亮光标所在行
	set hlsearch " 搜索关键词高亮
	set cmdheight=2 " 设置命令行高度
	set foldenable			  " 开始折叠
	set foldmethod=syntax	   " 设置语法折叠
	set foldcolumn=0			" 设置折叠区域的宽度
	setlocal foldlevel=1		" 设置折叠层数为
	setlocal noswapfile " 关闭临时文件
	set wildmenu " 启用文本模式的菜单
	set guioptions-=m " 关闭菜单栏
	set guioptions-=T " 关闭工具栏
	set laststatus=2
	set statusline=YUKI.N>\ こんにちは\ %m%r\ %=
	set statusline+=\ %{&ff}\ %Y\ 0x%02.2B
	set statusline+=\ %-21(%11(%l/%L%),%-3v\ %P%)
	"set smartindent			 " 开启新行时使用智能自动缩进
	"set cindent
	" set nowrap				  " 不自动换行
	"set autoindent
	" set textwidth=76		  " 自动换行
	" ==========通用设置end================
	" ==========Plugin start================
	filetype plugin indent on   " 开启插件
	" filetype on 命令打开文件类型检测功能
	" filetype plugin on 允许vim加载文件类型插件。
	" filetype indent on 允许vim为不同类型的文件定义不同的缩进格式。可继续设置
	"
	set completeopt=longest,menu " 补全
	
	" 加速补全
	" supertab
	" 在输入变量名或路径名等符号中途按Tab键,得到以前输入过的符号列表,并通过Tab键循环选择。
	"0 - 不记录上次的补全方式
	"1 - 记住上次的补全方式,直到用其他的补全命令改变它
	"2 - 记住上次的补全方式,直到按ESC退出插入模式为止
	let g:SuperTabRetainCompletionType=2
	let g:SuperTabDefaultCompletionType=""
	"
	" vim配置及自动补全插件neocomplcache
	" 使用缓存,自动补全时效率高、生成的关键词列表准确等优点。
	let g:neocomplcache_enable_at_startup=1
	
	" Remove trailing whitespace when writing a buffer, but not for diff files.
	" 自动去除无效空白,包括行尾和文件尾
	" @see http://blog.bs2.to/post/EdwardLee/17961
	function RemoveTrailingWhitespace()
	 if &ft != "diff"
	  let b:curcol = col(".")
	  let b:curline = line(".")
	  silent! %s/\s\+$//
	  silent! %s/\(\s*\n\)\+\%$//
	  call cursor(b:curline, b:curcol)
	 endif
	endfunction
	autocmd BufWritePre * call RemoveTrailingWhitespace()

有时你需要复制粘贴,不需要自动缩进,可以使用这个命令进行临时取消自动缩进::set noai


1 2 3 4 5 150 151 152 153 154