Posts Tagged: bash


27
二 12

从apachectl脚本中学到的一个技巧

bash中,使用[ -z "$val" ]和[ -n "$val" ]来判断变理val(字符串)为空、不为空。

但是,有时候,我们希望排除空格以及换行符,认为val为空格或者换行符时,作为空来处理。

在/usr/sbin/apachectl中,有一段有意思的写法:
# Set the maximum number of file descriptors allowed per child process.
if [ "x$ULIMIT_MAX_FILES" != "x" ] ; then
$ULIMIT_MAX_FILES
fi

本来,我以为这里是为了“去除空格及换行符以及不可见字符对判断的影响,但是经过几次测试,用引号引用之后,这样写与使用-z与-n来判断ULIMIT_MAX_FILES为空或不为空效果一样,空格、或者换行符,依然能影响判断结果。

看来作者这里的意思本来就是判断不为空吧。
正确的排除空格、换行符及其他不可打印字符影响的判断应该这样写:
if [ x$ULIMIT_MAX_FILES != "x" ] ; then
$ULIMIT_MAX_FILES
fi
这样,当ULIMIT_MAX_FILES=” “或者ULIMIT_MAX_FILES=’
‘都不会影响得到正确的逻辑结果了(作者的逻辑并没有错,是我理解错了)。

PS:另外,这样写的一个好处是,对bash的要求低,比如可以允许/bin/sh不支持[] 的test语法。


28
六 11

bash中getopts的用法

getopts 用于解析命令行参数。
用法: getopts 选项字符串 名称 [参数]
举个例子:
一个a.sh内容如下
#!/bin/bash
usage() {
cat << -EOF-
Usage:
$0 -I interface -i ipaddr

-EOF-
exit 1
}
while getopts “I:i:” opt ; do
case $opt in
I) interface=$OPTARG ;;
i) IP=$OPTARG ;;
?) usage ;;
done

if [[ -z "$interface" || -z "$ip" ]] ; then
usage
else
ifconfig $interface $ip
fi

其中:
1. 选顶字符串中,后面跟”:”(冒号)表示后面要跟一个参数,这个参数应用空格与选项隔开;这个参数保存在OPTARG变量中。
2. 选项字符串以”:”开始,将打开“沉默错误报错方式”,不能识别的选择等,将不打印错误信息。
3. getopts 将使用三个变量:name, OPTIND, OPTARG,OPTERR.
OPTERR :如果OPTERR=0, shell将禁用“错误提示输出”,即使选项字符串的开头不是冒号。
OPTARG : 当getopts解析到“选项”时,将指定的参数保存到这个变量中。
OPTIND : 存储$* 位置参数中的位置,随着getopts的处理而被getopts修改。

  


25
十二 10

bash 4

1. 从 command_not_found_handle 说起

发现Ubuntu(以及openSUSE)都提供了一个非常“友好”的功能:当你输入错误的、不存在的命令的时候,系统(实际上是bash调用应用程序)会给出相当有“建设性的”建议:

hongchuan@ubuntu:~$ gvim
程序“gvim”已包含在下列软件包中:
* vim
* vim-gnome
* vim-tiny
* vim-gtk
* vim-nox
请尝试:sudo apt-get install <选定的软件包>

hongchuan@ubuntu:~$ mc
程序“mc”尚未安装。  您可以使用以下命令安装:
sudo apt-get install mc

不访先思考一下Ubuntu是如何实现这个功能的。

………………………………

是的,要“捕获”这个“错误”,通过bash无疑是最直接和高效的。

……………………

bash 4提供了一个command_not_found_handle“钩子”,当bash遇到“命令找不到”时,假如这个函数被“定义”,则command_not_found_handle $1 被执行。

Ubuntu的command_not_found_handle是这样定义的:

command_not_found_handle () {
if [ -x /usr/lib/command-not-found ]; then
/usr/bin/python /usr/lib/command-not-found — $1;
return $?;
else
if [ -x /usr/share/command-not-found ]; then
/usr/bin/python /usr/share/command-not-found — $1;
return $?;
else
return 127;
fi;
fi
}

/usr/lib/command_not_found是一个用Pythonp写的处理“错误”、并提供“建议”的程序。

随便提一下,我当前的Ubuntu版本是10.10(lsb_release -r ),bash是4.1.5(1)-release(echo $BASH_VERSION),当前系统提供的bash手册中,完全没有提到command_not_found_handle,而GNU网站上的bash手册中则有详细说明:Command-Search-and-Execution

2. Coprocesses (协进程/异步支持)

bash 4新增的内置命令coproc,用于在后台启动一个子shell,并将command放在子shell中执行,在后台执行的进程称作coprocess(协进程)。当前shell不需要等待command执行完成,于是有“异步”之功效。coproc command与command &某种程度上效果相同。

如何使用 coproc :

举个例子,执行  coproc  COMMAND,那么 coproc 会做几件事情:

1.在后台启动子shell,PID值放在变量 COPROC_PID中。
2.在子shell中执行COMMAND。
3.建立两个“双向管通”,管道的文件描述符为${COPROC[0]}和${COPROC[1]}。

这样,就建立了一个“父进程”与“子进程”之间进行通信的方式(管道)。其中 ${COPROC[0]} 连接着“协进程”的“标准输出”,${COPROC[1]}连接着 “协进程”的“标准输入”。父进程通过${COPROC[0]}读取“协进程”的输出,通过${COPROC[1]}向子进程发送数据。

应用举例:
#!/bin/bash
rw() {
while read line ; do
echo $line >> log.txt
done
}
coproc  rw
cat >&${COPROC[1]}

这个程序在前台启动一个cat,后台启动一个read的循环,读取前台的输入并将输入写入log.txt文件。

另一个:

#!/bin/bash
process_line() {
local line=$@
echo “$line”
}
coproc tail -f /var/log/messages
while read line ; do
process_line “$line”
done <&${COPROC[0]}

“协进程”逐行输出/var/log/messages新增的行,前台程序逐行处理。

实际上,在bash 3中,在没有提供coproc的环境里,也可以使用这样的“思想”,实现“shell的多进程编程”。

3. 关联数组

bash支持的新“数据结构”,类似于Python的字典,PHP的关联数组。就是
p[name]=’hongchuan’
p[age]=28
p[site]=’http://www.bsdmap.com’

可以使用字符串作为数组的下标,引用值时${p[name]},${p[site]},${p[age]}。

使用“关链数组”需要事先声明:
declare -A p

4. 大小写转换

declare -l hostname
declare -u  MAC

声明变量hostname为小写,当执行hostname=”WWW.BSDMAP.COM”时,hostname的值为www.bsdmap.com。
声明变量MAC为大写,当MAC=”00:1b:2f:4c:19:7e”,MAC的值为 00:1B:2F:4C:19:7E。

5.  前置0

bash3中,为了产生0021这样的数,我是这么做的

for vlan in {1..10}; do
vlan_id=`printf %4d vlan|sed ‘s/ /0/g’`
……
done

bash4中,可以这样:
for vlan_id in {0001..10}; do
……
done

6. mapfile (readarray)

将文件装入数组
mapfile aa < log.txt
将文件log.txt以行为单位赋值给数组aa
callback() {
echo ${aa[@]}
}

mapfile -C callback -c1 aa < log.txt

help mapfile 更多详情。这个mapfile功能相当适用。

7. 新增两个重定向简写符

&>> 和 |&

&>> 等价于 >> file 2>&1

|&     等价于 2>&1 |

更多新特性,见官方手册。

 

 

有用的链接:

http://www.gnu.org/software/bash/
http://wiki.bash-hackers.org/bash4

http://www.gnu.org/software/bash/manual/bashref.html


6
十一 10

在脚本中使用ssh时的几个注意事项

1. 超时设置
-o ConnectTimeout=3

2. 重定项标准输入到/dev/null
-n
当使用这样的形式时 (使用 key 认证):

    while read line ; do
        ip=$(awk '{print $1}' < << $line )
        ssh -n -o ConnectTimeout=3 $i uptime
    done < file

假如此时不使用 -n ,则只有第一行会被处理。

3. 批处理模式,在脚本中使用再合适不过(使用 key 认证)
-o BatchMode=yes
当 key 认证不成功时,有可能会弹出“密码认识”,从而影响脚本运行下去,此时可以打开 BatchMode模式。

4. 遇到未知主机:

-o StrictHostKeyChecking=no

当遇到未知的主机公钥时,自动接受key。

5. 糟遇远程主机连接后无响应:

当设置了 BatchMode 时 ServerAliveInterval 默认被设置成 300 秒(服务端无数据传回的持续时间)。
ServerAliveCountMax相当于是重试的次数,比如下面的例子,15秒 x 3 = 45 秒,即当 45 秒后,真正超时断开。
TCPKeepAlive打开时,便于发现网络的断开。当网络故障(比如路由器坏掉)或者远端开机、死机时,连接会主动断开,否则的话,将会等待相当一段时间后才会断开。

-o ServerAliveInterval=15
-o ServerAliveCountMax=3
-o TCPKeepAlive=yes

需要注意的是,这里的超时、无响应,仅是 ssh或者sshd无影响,假如是在远程上执行程序,程序无响应,则不能处理此时的超时,解决方法见上一篇《在Shell中实现异步》。

 


6
十一 10

在Shell中实现异步

在shell脚本中,如何实现异步的超时设置功能?

关键部分的代码如下:

            scp ../local_hw_oob_init.sh $i: &>/dev/null
            ssh $i /root/local_hw_oob_init.sh &>/dev/null &
            SSHPID=$!
            { sleep 60 ; kill -9 $SSHPID ; } &
            KILLPID=$!
            wait $SSHPID
            if [ $? = 0 ] ; then
                kill -9 $KILLPID
                echo -n "SSH_OK,"
            else
                echo -n "SSH_KILLED,"
            fi

首先,将主任务 TASK1 放到后台,记录 TASK1_PID;然后立即在后台再运行第二个任务 { sleep 60 ; kill -9 $TASK1_PID ; } 到后台,然后启动一个 waith $TASK1_PID 的指令。

假如在 60 秒内,TASK1 还没有返回,就会被第二个后台的任务 kill 掉,此时,waith 收到 TASK1 的退出状态,并返回。
假如在 60 秒内,TASK1 返回,则wait之后,紧跟着 kill 掉第二个任务。


10
二 10

系统调用exec和fork

Exec 与 fork 是 UNIX 中的两个系统调用,UNIX 程序利用它们来创建新的进程。

由一个进程产生 (spawn) 另一个进程 ,可能是进程产生后用新进程取代它,即exec;或者如何需要保留这个进程,那就复制一个进程,即 fork 。

举个例子:Getty 进程监测一个串行端口 (tty),提供了一个 “login:” 提示符,当用户输入登录名回车之后,getty的任务就完成了;它执行 (exec) 了 login 命令,当 login 检测密码输入正确之后,它执行 (exec) 登录 shell 。一旦用户启动另一个程序,shell 程序就会派生 (fork) 自己,并且这个复本将执行 (exec) 用户所要运行的任何程序。


17
九 08

几个性能工具备忘

top:

* A: PID        = Process Id
* E: USER       = User Name
* H: PR         = Priority
* I: NI         = Nice value
* O: VIRT       = Virtual Image (kb)
* Q: RES        = Resident size (kb)
* T: SHR        = Shared Mem size (kb)
* W: S          = Process Status
* K: %CPU       = CPU usage
* N: %MEM       = Memory usage (RES)
* M: TIME+      = CPU Time, hundredths
b: PPID       = Parent Process Pid
c: RUSER      = Real user name
d: UID        = User Id
f: GROUP      = Group Name
g: TTY        = Controlling Tty
j: P          = Last used cpu (SMP)
p: SWAP       = Swapped size (kb)
l: TIME       = CPU Time
r: CODE       = Code size (kb)
s: DATA       = Data+Stack size (kb)
u: nFLT       = Page Fault count
v: nDRT       = Dirty Pages count
y: WCHAN      = Sleeping in Function
z: Flags      = Task Flags <sched.h>
* X: COMMAND    = Command name/line

Flags field:
0×00000001  PF_ALIGNWARN
0×00000002  PF_STARTING
0×00000004  PF_EXITING
0×00000040  PF_FORKNOEXEC
0×00000100  PF_SUPERPRIV
0×00000200  PF_DUMPCORE
0×00000400  PF_SIGNALED
0×00000800  PF_MEMALLOC
0×00002000  PF_FREE_PAGES (2.5)
0×00008000  debug flag (2.5)
0×00024000  special threads (2.5)
0x001D0000  special states (2.5)
0×00100000  PF_USEDFPU (thru 2.4)

进程的优先级和nice级别
进程优先级是一个决定进程被CPU执行优先顺序的参数,内核会根据需要调整这个值。Nice值是一个对优先权的限制。进程优先级的值不能低于nice值。(nice值越低优先级越高)
进程优先级是无法去手动改变的,只有通过改变nice值去间接的调整进程优先级。如果一个进程运行的太慢了,你可以通过指定一个较低的nice值去为它分配更多的CPU资源。当然,这意味着其他的一些进程将被分配更少的CPU资源,运行更慢一些。Linux支持nice值的范围是19(低优先级)到-20(高优先级),默认的值是0。如果需要改变一个进程的nice值为负数(高优先级),必须使用su命令登陆到root用户。下面是一些调整nice值的命令示例,
以nice值-5开始程序xyz
#nice –n -5 xyz

改变已经运行的程序的nice值
#renice level pid

将pid为2500的进程的nice值改为10
#renice 10 2500

vmstat:

·process(procs)
r:等待运行时间的进程数量
b:处在不可中断睡眠状态的进程
w:被交换出去但是仍然可以运行的进程,这个值是计算出来的
·memoryswpd:虚拟内存的数量
free:空闲内存的数量
buff:用做缓冲区的内存数量
·swap
si:从硬盘交换来的数量
so:交换到硬盘去的数量
·IO
bi:向一个块设备输出的块数量
bo:从一个块设备接受的块数量
·system
in:每秒发生的中断数量, 包括时钟
cs:每秒发生的context switches的数量
·cpu(整个cpu运行时间的百分比)
us:非内核代码运行的时间(用户时间,包括nice时间)
sy:内核代码运行的时间(系统时间)
id:空闲时间,在Linux 2.5.41之前的内核版本中,这个值包括I/O等待时间;
wa:等待I/O操作的时间,在Linux 2.5.41之前的内核版本中这个值为0

iostat:

%user:user level(应用)的CPU占用率情况
%nice:加入nice优先级的user level的CPU占用率情况
%sys:system level(内核)的CPU占用情况
%idle:空闲的CPU资源情况

Device:块设备名
Tps:设备每秒进行传输的数量(每秒的I/O请求)。多个单独的I/O请求可以被组成一个传输操作,因为一个传输操作可以是不同的容量。
Blk_read/s, Blk_wrtn/s:该设备每秒读写的块的数量。块可能为不同的容量。
Blk_read, Blk_wrtn:自系统启动以来读写的块设备的总量。

块可能为不同的容量。块的大小一般为1024、2048、4048byte。可通过tune2fs或dumpe2fs获得:
# tune2fs -l /dev/hda1|grep ‘Block size’
Block size:               4096
# dumpe2fs -h /dev/hda1|grep ‘Block size’
dumpe2fs 1.35 (28-Feb-2004)
Block size:               4096


11
七 08

shell中的判断

[ -f $file ]
像这们的句子,应该写成
[ -f "${file}" ] 这样可以
第1:避免变量file里包含特殊符号的情况。(当然这样也不是万能的)
第2:万一还有一个变量f,那么$file就会出现异常。

2010-02-01
apachectl 脚本中判断变量不为空:
$ULIMIT_MAX_FILES=”ulimit -S -n `ulimit -H -n`”
if [ "x$ULIMIT_MAX_FILES" != "x" ] ; then
    $ULIMIT_MAX_FILES
fi


10
七 08

BASH中字符串的处理

$x=abcd

[得到长度]

方法1:
$expr length $x
4

方法2:
$echo ${#x}
4

方法3:
$expr “$x” : “.*”    #expr的手册信息
4           #STRING : REGEXP anchored pattern match of REGEXP in STRIN

[查找子串]

$expr index $x “b”
2
$expr index $x “a”
1

[得到子字符串]

方法1:
#expr startpos length
$expr substr “$x” 1 3
abc
$expr substr “$x” 1 5
abcd
$expr substr “$x” 2 5
bcd

方法2:
#${x:pos:lenght}
$echo ${x:1}
bcd
$echo ${x:2}
cd
$echo ${x:0}
abcd
$echo ${x:0:1}
a

[匹配正则表达式]

[打印匹配长度]
$expr match $x “.”
1
$expr match $x “abc”
3
$expr match $x “bc”
0

[字符串的掐头去尾]

$x=aabbaarealwwvvww
$echo “${x%w*w}”
aabbaarealwwvv
$echo “${x%%w*w}”
aabbaareal
$echo “${x##a*a}”
lwwvvww
$echo “${x#a*a}”
bbaarealwwvvww

其中 , # 表示掐头, 因为键盘上 # 在 $ 的左面。
其中 , % 表示%, 因为键盘上 % 在 $ 的右面。
单个的表示最小匹配,双个表示最大匹配。
也就是说,当匹配的有多种方案的时候,选择匹配的最大长度还是最小长度。

[字符串的替换]

$x=abcdabcd
$echo ${x/a/b} # 只替换一个
bbcdabcd
$echo ${x//a/b} # 替换所有
bbcdbbcd


10
七 08

SHELL程序的不同执行方式

在当前shell环境运行:继承并影响当前环境。
. a.sh
source a.sh
实际上,”.”号与source的效果是一样的。
在当前shell运行时,需要注意,shell程序会影响、改变当前shell的环境。假如a.sh中有exit指令,那么用户将退出shell登录。

启动新的shell执行:只继承export输入的变量,并切不影响父进程的环境。exit指令只是退出新启动的shell。
bash a.sh
bash <a.sh
chmod+x a.sh
./a.sh

在( ) 中运行:executed in a subshell environment

也是在新的进程中运行。但是继承关系有点复杂,实验如下:

####START####
$su -
#chmod +x a.sh
#cat a.sh
echo $a
exit
#a=FreeBSD
#(./a.sh)

#(source a.sh)
FreeBSD
#(. a.sh)
FreeBSD
####END####

a.sh中的exit均没有影响到当前shell,但是却继承了当前shell的变量,但是改变a的操作却不能影响当前shell下a的值。

另外:
exec ./a.sh
关于exec:
exec: exec [-cl] [-a name] file [redirection ...]
Exec FILE, replacing this shell with the specified program.
If FILE is not specified, the redirections take effect in this
shell. If the first argument is `-l’, then place a dash in the
zeroth arg passed to FILE, as login does. If the `-c’ option
is supplied, FILE is executed with a null environment. The `-a’
option means to make set argv[0] of the executed process to NAME.
If the file cannot be executed and the shell is not interactive,
then the shell exits, unless the shell option `execfail’ is set.

man bash

Compound Commands
A compound command is one of the following:

(list) list  is  executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below).  Variable assignments and builtin commands that affect the shell’s environment do not remain in effect after the command completes.  The return status is the exit status  of list.

{ list; }
list  is simply executed in the current shell environment.  list must be terminated with a newline or semicolon.  This is known as a group command.  The return status is the exit status of list.  Note that unlike the metacharacters ( and ), { and } are reserved words and must occur where a reserved word is permitted to be recognized.  Since they do not cause a word break, they must be sep-arated from list by whitespace.
Command Substitution
Command substitution allows the output of a command to replace the command name.  There are two forms:

$(command)
or
‘command‘

Bash performs the expansion by executing command and replacing the command substitution with the standard output of the command, with any trailing  newlines  deleted.  Embedded newlines are not deleted, but they may be removed during word splitting.  The command substitution $(cat file) can be replaced by the equivalent but faster $(< file).

When the old-style backquote form of substitution is used, backslash retains its literal meaning except when followed by $, ‘, or \.  The first backquote not preceded by a backslash terminates the command substitution.  When using the $(command) form, all characters between the parentheses make up the command; none are treated specially.

Command substitutions may be nested.  To nest when using the backquoted form, escape the inner backquotes with backslashes.

If the substitution appears within double quotes, word splitting and pathname expansion are not performed on the results.

COMMAND EXECUTION ENVIRONMENT
The shell has an execution environment, which consists of the following:

·      open files inherited by the shell at invocation, as modified by redirections supplied to the exec builtin

·      the current working directory as set by cd, pushd, or popd, or inherited by the shell at invocation

·      the file creation mode mask as set by umask or inherited from the shell’s parent

·      current traps set by trap

·      shell parameters that are set by variable assignment or with set or inherited from the shell’s parent in the environment

·      shell functions defined during execution or inherited from the shell’s parent in the environment

·      options enabled at invocation (either by default or with command-line arguments) or by set

·      options enabled by shopt

·      shell aliases defined with alias

·      various process IDs, including those of background jobs, the value of $$, and the value of $PPID

When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate  execution  environment  that consists of the following.  Unless otherwise noted, the values are inherited from the shell.

·      the shell’s open files, plus any modifications and additions specified by redirections to the command

·      the current working directory

·      the file creation mode mask

·      shell variables and functions marked for export, along with variables exported for the command, passed in the environment

·      traps caught by the shell are reset to the values inherited from the shell’s parent, and traps ignored by the shell are ignored

A command invoked in this separate environment cannot affect the shell’s execution environment.

Command substitution, commands grouped with parentheses, and asynchronous commands are invoked in a subshell environment that is a duplicate of the shell environment, except that traps caught by the shell are reset to the values that the shell inherited from its parent  at invocation.   Builtin  commands  that are invoked as part of a pipeline are also executed in a subshell environment.  Changes made to the subshell environment cannot affect the shell’s execution environment.

If a command is followed by a & and job control is not active, the default standard input for the command is the  empty  file  /dev/null. Otherwise, the invoked command inherits the file descriptors of the calling shell as modified by redirections.