标签 Shell 下的文章

Shell单引号和双引号

问题一:

/etc/hosts
192.168.1.1 www.firefoxbug.net

$ awk '{print $1}' /etc/hosts ==>> 打印IP
$ awk "{print $1}" /etc/hosts ==>> 打印整行

这两个打印出来是不一样的,同样是在一个bash下执行。因为$1在双引号里面,是属于弱引用,$1被shell解析了,解析出来是空,可以看看下面的实验。
$ echo $1
$ awk "{print }" /etc/hosts

问题二:

echo '\'' ==>> 等待输入
echo "\"" ==>> 输出"

在单引号的强引用下,所有的转义都已经失效了,所以第一个echo在等待第二个单引号的匹配。

shell数组和字典

#!/bin/bash

echo "shell定义字典"
#必须先声明
declare -A dic
dic=([key1]="value1" [key2]="value2" [key3]="value3")

#打印指定key的value
echo ${dic["key1"]}
#打印所有key值
echo ${!dic[*]}
#打印所有value
echo ${dic[*]}

#遍历key值
for key in $(echo ${!dic[*]})
do
        echo "$key : ${dic[$key]}"
done

echo "shell定义数组"

#数组
list=("value1" "value2" "value3")
#打印指定下标
echo ${list[1]}
#打印所有下标
echo ${!list[*]}
#打印数组下标
echo ${list[*]}
#数组增加一个元素
list=("${list[@]}" "value3")

OpenCDN流量采集bug修复

OpenCDN节点端Nginx把节点流量信息以下面这种格式输出到管道bandwidth.pipe。

nginx.conf
log_format  bandwidth   '[$time_local] $host $body_bytes_sent $upstream_cache_status';

vhost配置
access_log /usr/local/opencdn/pipe/bandwidth.pipe bandwidth;

在管道的另一段读取流量信息,然后输出到下面的文件里(shell守护)
/usr/local/opencdn/stream/域名/
-| year_month_day_hour_minute
比如
/usr/local/opencdn/stream/www_firefoxbug_net
2013_Sep_30_12_10

管控端采集流量就是通过守护,以http://%s:%s/ocdn/stream?token=%s&domain=%s来抓取流量,处理后导入数据库。

之前OpenCDN产生一个小需求,使用CDN加速的小伙伴们添加了一个域名比如是aaa.com,但是他还加了别名,xxx.aaa.com(可以是二级域名)。OpenCDN节点端会在Nginx的vhost配置server_name加一个aliasname。但是小伙伴访问都是用这个aliasname访问的。而根据上面的逻辑管控端就没有办法采集到aliasname的流量。
怎么办?在节点端加对域名的流量路径加个软链接就可以,那么aliasname访问的流量也会记录到原站点的流量下。

中间还遇到一个小问题

$ mkdir test1
$ ln -s test1 test2
$ [ -L test2 ] ; echo $?
0
$ [ -L test2/ ] ; echo $?
1

为什么测试目录链接文件加/和没加/有区别呢?得@smallfish指点,大概明白了。软链接是新建一个inode节点的,这个inode节点对应的block记录的是原目录的路径,从软链接到原目录路径其实是比正常访问多一层的。我想linux的设计就是加/来区别吧,没加/那么就是读取普通的目录文件一样,加了/就是change到原路径里去了。

base64编码

By firefoxbug

shell base64编码


[root@firefoxbug] str="hello world"

[root@firefoxbug] echo -n "$str" | base64

aGVsbG8gd29ybGQ=

[root@firefoxbug] echo "$str" | base64 注意要加 -n ,不然会有换行,编码就有问题了。

aGVsbG8gd29ybGQK

shell base64解码


-d :decode

[root@firefoxbug]  echo -n "$str" | base64 | base64 -d

hello world

python base64编码


[code]
#!/usr/bin/python
#-*-coding:utf-8-*-

import base64
sour_str = "hello world"
print base64.encodestring(sour_str)
[/code]

python base64解码


[code]
#!/usr/bin/python
#-*-coding:utf-8-*-

import base64
sour_str = "hello world"
decode_str = base64.encodestring(sour_str)
print base64.decodestring(decode_str)
[/code]

shell字符串操作

By firefoxbug
string="abcABC123ABCabc"
字符串长度:

echo ${#string}			#15
echo `expr length $string`	#15

索引


用法:expr index $string $substring

expr index $string "ABC"	#4

提取子串


用法:${string:position}

echo ${string:3}		#ABC123ABCabc

提取指定长度子串
用法:${string:position:length}

echo ${string:3:3}		#ABC

从末尾提取子串
用法:
${string: -length}注意有空格
${string:(-length)}

echo ${string:(-3)}		#abc

子串剔除

从左边开始截吊第一个匹配的$substring
用法:${string#*substring}

echo ${string#*abc}		#ABC123ABCabc
substr="abc";echo ${string#*$substr}	##ABC123ABCabc

从左边开始截吊最后一个匹配的$substring
[php]
echo ${string##*abc} #
[/php]

从右边开始截吊第一个匹配的$substring
用法:${string%substring*}

echo ${string%abc*}		#abcABC123ABC

从右边开始截吊最后一个匹配的$substring

echo ${string%%abc*}		#

子串替换


用$replacement来替换第一个匹配的$substring
用法:${string/substring/replacement}

echo ${string/ABC/XYZ}		#abcXYZ123ABCabc

用$replacement来替换全部匹配的$substring
用法:${string//substring/replacement}

echo ${string//ABC/XYZ}		#abcXYZ123XYZabc

如果$substring匹配$string的开头部分,那么就用$replacement来替换
用法:${string/#substring/replacement}

echo ${string/#abc/xyz}		#xyzABC123ABCabc

如果$substring匹配$string的结尾部分,那么就用$replacement来替换
用法:${string/%substring/replacement}

echo ${string/%abc/xyz}		#abcABC123ABCxyz

Linux注册系统服务

注册一个系统服务,开机自启动.

1 脚本编写


#vim test.sh

#!/bin/bash

#description: hello.sh
#chkconfig: 2345 20 81

EXEC_PATH=/usr/local/
EXEC=hello.sh
DAEMON=/usr/local/hello.sh
PID_FILE=/var/run/hello.sh.pid

. /etc/rc.d/init.d/functions

if [ ! -x $EXEC_PATH/$EXEC ] ; then
       echo "ERROR: $DAEMON not found"
       exit 1
fi

stop()
{
       echo "Stoping $EXEC ..."
       ps aux | grep "$DAEMON" | kill -9 `awk '{print $2}'` >/dev/null 2>&1
       rm -f $PID_FILE
       usleep 100
       echo "Shutting down $EXEC: [  OK  ]"
}

start()
{
       echo "Starting $EXEC ..."
       $DAEMON > /dev/null &
       pidof $EXEC > $PID_FILE
       usleep 100
       echo "Starting $EXEC: [  OK  ]"
}

restart()
{
	stop
	start
}

case "$1" in
	start)
		start
		;;
	stop)
		stop
		;;
	restart)
		restart
		;;
	status)
		status -p $PID_FILE $DAEMON
		;;
	*)
		echo "Usage: service $EXEC {start|stop|restart|status}"
		exit 1
esac

exit $?

2注册服务

- 阅读剩余部分 -

inotify 安装

传统的rsync+crontab同步数据和实际会有差异,而inotify则基本可以达到实时的效果,当文件有任何变

动,就会触发inotify。

inotify 是一个 Linux 内核特性,它监控文件系统,并且及时向专门的应用程序发出相关的事件警告,比如删

- 阅读剩余部分 -

Mysql 数据库自动备份Shell脚本

#!/bin/bash
#Shell Command For Backup MySQL Database Everyday Automatically By Crontab

USER=root
PASSWORD="123456"
DATABASE="test"
HOSTNAME="192.168.156.61"

WEBMASTER=test@qq.com

BACKUP_DIR=/home/firefoxbug/mysql_back/ #备份文件存储路径
LOGFILE=/home/firefoxbug/mysql_back/data_backup.log #日记文件路径
DATE=`date '+%Y%m%d-%H%M'` #日期格式(作为文件名)
DUMPFILE=$DATE.sql #备份文件名
ARCHIVE=$DATE.sql.tgz #压缩文件名
OPTIONS="-h$HOSTNAME -u$USER -p$PASSWORD $DATABASE"
#mysqldump -help

#判断备份文件存储目录是否存在,否则创建该目录
if [ ! -d $BACKUP_DIR ] ;
then
        mkdir -p "$BACKUP_DIR"
fi

#开始备份之前,将备份信息头写入日记文件
echo " " >> $LOGFILE
echo " " >> $LOGFILE
echo "———————————————–" >> $LOGFILE
echo "BACKUP DATE:" $(date +"%y-%m-%d %H:%M:%S") >> $LOGFILE
echo "———————————————– " >> $LOGFILE

#切换至备份目录
cd $BACKUP_DIR
#使用mysqldump 命令备份制定数据库,并以格式化的时间戳命名备份文件
mysqldump $OPTIONS > $DUMPFILE
#判断数据库备份是否成功
if [[ $? == 0 ]]; then
	#创建备份文件的压缩包
	tar czvf $ARCHIVE $DUMPFILE >> $LOGFILE 2>&1
	#输入备份成功的消息到日记文件
	echo “[$ARCHIVE] Backup Successful!” >> $LOGFILE
	#删除原始备份文件,只需保 留数据库备份文件的压缩包即可
	rm -f $DUMPFILE
else
	echo “Database Backup Fail!” >> $LOGFILE
fi
#输出备份过程结束的提醒消息
echo “Backup Process Done”


放到crontable里面!十分钟执行一次
crontab -e
*/10 * * * * /tmp/auto_log.sh >/dev/null  2>&1

inotifywait实现目录监控

传统的rsync+crontab同步数据和实际会有差异,而inotify则基本可以达到实时的效果,当文件有任何变动,就会触发inotify。
inotify 是一个 Linux 内核特性,它监控文件系统,并且及时向专门的应用程序发出相关的事件警告,比如删除、读、写和卸载操作等。inotify安装完成之后会有两个命令,
inotifywait 和 inotifywatch。inotifywait用于等待文件或者文件集上的一个特定事件,可以监控任何文件或者目录位置,并且可以递归地监控整个目录树;inotifywatch 用于收集被监控的文件系统统计数据,包括每个inotify事件发生多少次等信息。

- 阅读剩余部分 -

linux 守护进程编程

守护进程简介


守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导装入时启动,在系统关闭时终止。Linux系统有很多守护进程,大多数服务都是通过守护进程实现的,同时,守护进程还能完成许多系统任务,例如,作业规划进程crond、打印进程lqd等(这里的结尾字母d就是Daemon的意思)。
由于在Linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才退出。如果想让某个进程不因为用户或终端或其他地变化而受到影响,那么就必须把这个进程变成一个守护进程。

- 阅读剩余部分 -

Stopped (tty output)

问题描述:


在一个shell脚本里,执行了需要I/O操作的程序,一旦把这个shell脚本放在后台执行,就会出现这个错误!

问题原因:


This signal is most commonly generated when a process cannot write to the controlling terminal

because it has been placed into the process background

问题解决:


网上说可以用 ./script < /dev/null &,但是我的脚本需要交互,所以还是挂了。

awk基础学习

awk 处理流的形式也是一行一行的,读取一行然后按照指定的模式进行处理,处理完成后默认输出到终端。

awk [-F fild:separator] 'command' filename
[-F fild-separator ]是可选的,awk默认以空格作为缺省的分隔符号,在脚本中可以通过FS=“X”来设定,X是任意分割符。
表达式匹配的特殊字符
\ ^ $ . [ ] | ( ) * + ?

awk 内置变量


ARGC 命令行参数个数
ARGV 命令行参数排列
ENVIRON 支持队列中系统环境变量的使用
FILENAME awk 浏览的文件名
FNR 浏览文件的记录数
FS 设置输入域分隔符,等价于命令行- F 选项
NF 浏览记录的域个数,用$NF可以表示最后一个域
NR 已读的记录数
OFS 输出域分隔符,OFS="#",输出分隔符就是#
ORS 输出记录分隔符
RS 控制记录分隔符

awk的正则表达式在//里面,模式匹配的sed差不多
找到所有含有root的行,打印第一个域
awk -F : '/root/{print $1}' /etc/passwd

找到行首是root的行,打印第一个域
awk -F : '/^root/{print $1}' /etc/passwd

用~来进行字段匹配,第一个域是root的行
awk -F : '$1~/root/' /etc/passwd
找到第一个域不是root的行
awk -F : '$1!~/root/' /etc/passwd

用print的时候,不同参数之间用逗号作分割符号,则输出以空格作为分割符号;若以空格作为参数分割符号,输出被不存在分隔符号
echo "etc/fire/test.c" | awk -F / '{print $1,$2,$3}'  ==>> home fire test.c
echo "etc/fire/test.c" | awk -F / '{print $1 $2 $3}'  ==>> homefiretest.c

开始结束都加hello,中间打印第一个域
awk -F: 'BEGIN{print "......hello.......\n"} {print $1} END{print ".........hello........\n"}' /etc/passwd

使用if语句进行匹配


找出第一个域是root的行,注意不能写成=,如果写出=,那么就把所有行的第一个域都赋值为root,所有行都匹配
awk -F: '{if($1=="root")} print $0' /etc/passwd

找出第三个域值是32的行
awk -F: '{if($3=="32") print $0}' /etc/passwd

不等于
找出第一个域不是root的行
awk -F: '{if($1!="root") print $0}' /etc/passwd


找出第一个域是root并且第五个域也是root的行
awk -F: '{if($1=="root"&&$5=="root") print $0}' /etc/passwd

NF:每行记录域的总个数
awk -F: '{print NF}' /etc/passwd

NR:已读记录域的数目
awk -F: '{print NR}' /etc/passwd

每行首加总域数和已读域数
awk -F: '{print NF,NR,$0}' /etc/passwd

在保证一行的域大于0,一个域是root的情况下,打印所在行号和行
awk -F: '{if(NF>0 && $1=="root") print NR,$0}' /etc/passwd

显示当前目录的名字,用$NF来显示最后一个域
pwd | awk -F / '{print $NF}'
显示一个目录的文件名字
echo "/home/firefoxbug/test.c" | awk -F / '{print $NF}'

找到第一个域是root行,并把第三个域加3,第四个域加4
可以用{}对匹配的行进行操作,若要执行多条语句,用;分割
awk -F: '{if($1=="root"){$3=$3+3;$4=$4+4;print $0}}' /etc/passwd

在每行最后添加一个域,域值是第三个域值和第四个域值的和
awk -F: '{$(NF+1)=$3+$4;print $(NF)}' /etc/passwd

统计当前目录下所有文件长度,首先排除目录,然后求得ls -l 的第五个域总和
ls -l | grep '^[^d]' | awk '{print $5;total=total+$5} END{print total}'

awk的printf函数,printf函数用法和C语言类似
awk 'BEGIN{printf ("%s\n","hello")}'

awk命令中传递参数
awk '模式' 变量=值 filename
比如查看 df -k 第四个域大于TARGET的项,其中TARGET是用户指定的
df -k | awk '{ if($4>TARGET) {printf ("%s : %d\n",$6,$4) } }' TARGET=650000

查看/etc/passwd下第一个域是USER的行,其中USER由用户指定
awk -F : '{if ($1==USER) {printf ("HAVE %s\n",$1) }}' USER=root /etc/passwd

trap linux

trap命令用于指定在接收到信号后将要采取的行动,trap命令的参数分为两部分,前一部分是接收到指定信号时将要采取的行动,后一部分是要处理的信号名.

信     号

说     明

HUP(1)

挂起,通常因终端掉线或用户退出而引发

INT(2)

中断,通常因按下Ctrl+C组合键而引发

QUIT(3)

退出,通常因按下Ctrl+\组合键而引发

ABRT(6)

中止,通常因某些严重的执行错误而引发

ALRM(14)

报警,通常用来处理超时

TERM(15)

终止,通常在系统关机时发送


一. trap捕捉到信号之后,可以有三种反应方式:
  1. 执行一段程序来处理这一信号
  2. 接受信号的默认操作
  3. 忽视这一信号

二. trap对上面三种方式提供了三种基本形式:
第一种形式的trap命令在shell接收到signal list清单中数值相同的信号时,将执行双
引号中的命令串:
trap 'commands' signal-list
trap "commands" signal-list

第二种形式的trap命令,为了恢复信号的默认操作:
trap signal-list

第三种形式的trap命令允许忽视信号:
trap " " signal-list

在第一种形式中,执行命令,对于双引号和单引号是有区别的。

#/bin/bash

#忽略信号
#trap " " 2

#双引号,shell第一次设置信号的时候就执行命令和变量的替换,时间不变
trap "echo `date`:can not terminate by ctrl+C" 2		

#单引号,要在shell探测到信号来的时候才执行命令和变量的替换,时间一直变
trap 'echo `date`:can not terminate by ctrl+C' 2		

while [ 1 ]
do
	echo -n "input a num : "
	read num 
	if [ $num -eq -1 ]
	then
		echo "bye"
		break
	fi
	echo "you have enter $num"
done

shell执行过程简介

[root@fire cgi-bin]# export TEST="Hello
> Wolrd
> From
> Linux"
[root@fire cgi-bin]# echo $TEST 
Hello Wolrd From Linux
[root@fire cgi-bin]# echo "$TEST" 
Hello
Wolrd
From
Linux
[root@fire cgi-bin]# echo '$TEST'
$TEST

上面是测试,一直以来对hard quote(单引号)和soft quote(双引号),弄得不是很懂,今早又碰到这个问题。

下面来解释上面的测试代码:

首先从根本出发,shell的任务的就是解释用户的输入,将一大堆的字符串做处理。处理成

command_program options  arguments

这样的格式,说白了就是把一个整的字符串分割成一个一个的字段("word"),那么shell是通过什么分割的呢?通过把<space>,<enter>,<tabs>这三种作为分隔符,拆分成字段。而shell是通过最后用户输入一个<enter>键来标明用户输入的结束,一旦处理到<enter>键,shell就得到了通知:用户已经输入完成了,我可以执行命令去了。之后shell根据默认的IFS处理,进行变量的替换之类的工作,最后提交给程序执行。

看上面的第一个命令

[root@fire cgi-bin]# export TEST="Hello
> Wolrd
> From
> Linux"

因为soft quote能屏蔽<enter>,<enter>键所产生的CR字符不会被shell当作结束符,所以shell继续等待用户的输入,等到用户输入后面的双引号,再输入<enter>键,这时候shell读到了CR字符,就知道用户输入的结束了,于是就开始执行命令。这个时候TEST里面的内容其实是:

Hello<CR>World<CR>From<CR>Linux

注意“Linux”后面是没有<CR>的,之所以会有换行,是因为echo执行结束后会自动送入一个<CR>!

再看第二条命令:

echo $TEST

这时候没有hard quote或者soft quote,shell得到的命令变成

echo Hello<CR>World<CR>From<CR>Linux

现在再去看shell的执行过程,shell就是“把一个整的字符串拆分成字段,字段分割的标识是<space>,<enter>,<tabs>,然后根据默认的IFS处理”,shell读到<CR>,通通都按照默认的IFS(空格)处理。所以处理的结果就可想而知了:所有的<CR>变成了<space>。最后得到的最终命令,按照

command_program options  argumentscommand_program 就是 echo ,options 是无 ,arguments 就是 Hello World From Linux

再来看第三条命令

echo "$TEST"

这个存在soft quote,shell得到命令就是

echo "Hello<CR>World<CR>From<CR>Linux"

这里shell不能处理什么,soft quote里面就是一个参数,最终的命令是

command_program 就是 echo ,options 是无 ,arguments 就是 Hello<CR>World<CR>From<CR>Linux

最后看第四条命令

echo '$TEST'

这个最简单, TEST变量在hard quote里面,所有的meta都被屏蔽了,所以按照最终的命令就是

command_program 就是 echo ,options 是无 ,arguments 就是 $TEST

find+*的问题

不久前做移植的时候想把某个目录下的C文件都找出来,然后拷贝下,结果一直报错,我用的是*.c作为pattern。今天看论坛的时候知道为什么了。

$ ls

test2.c  test.c  test.txt

目录下有两个.c文件,还有一个.txt文件

$ find . -name *.c

error : find: 路径必须在表达式之前: test.c
用法: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]

$ find . -name *.txt

./test.txt

$ find . -name "*.c"

./test2.c
./test.c

原因:

若是不加引号 $ find . -name *.txt 成功,而不加引号的 $ find . -name*.c 出错了,得看看shell是怎么执行这句话的,首先shell读到了 *.txt 和 *.c,因为 * 是shell认为的meta (理解为特殊字符),所以就先解释*txt 先执行成 test.txt,传递给find,find就去执行自己的操作,根据pattern发现符合要求。而 *.c 被shell翻译成了test.c和test2.c,这时候命令就变成了 find . -name test.c test2.c 这就出错了!因为find -name 选项后面只能支持一个文件的搜索。所以对于test2.c是前面没有选项,就报错了!

对于有引号的 find . -name "*.c",shell读到"*.c"的时候,会当作参数处理,传递给find,find之后会自己处理 *.c,可以查看 man find ,里面有

The metacharacters (`*', `?', and `[]') match a `.'