[shell]二十问 - 迁
第一问: 执行环境和vi
目录环境
相对路径
CURDIR=$(cd $(dirname $0); pwd .)
带文件名的和路径
$CURDIR/$(basename $0)"
取超链接目录绝对路径
$(dirname $(readlink -f $0))
引用的环境变量
if [ -f /etc/init.d/functions ]; then . /etc/init.d/functions fi
daemon echo_success success 这种命令都是这个脚本里面定义的
用户环境变量也要带入
if [ -f "~/.bash_profile" ]; then
source ~/.bash_profile
fi
第二问:shell 编码基础
一、shell内置变量
1) $FUNCNAME
函数的名字,类似于C语言中的内置宏__func__,但宏__func__ 只能代表当前所在的函数名,
而$FUNCNAME的功能更强大,它是一个数组变量,其中包含了整个调用链上所有的函数的名字,故变量
${FUNCNAME [0]} 代表shell脚本当前正在执行的函数的名字,
${FUNCNAME[1]} 代表调用函数${FUNCNAME[0]}的函数的名字,依此类推。
2) $BASH_SOURCE
shell脚本源文件名,与FUNCNAME相对应
3) $BASH_LINENO
代表shell脚本的当前行号,类似于C语言中的内置宏__LINE__,与FUNCNAME相关联
BASH_LINENO[$i] 指示的是 FUNCNAME[$i + 1]被调用的位置
4) $PS4
第四级提示符变量$PS4 , $PS4的值将被显示在“-x”选项输出的每一条命令的前面。
在Bash Shell中,缺省的$PS4的值是"+"号。(现在知道为什么使用"-x"选项时,输出的命令前面有一个"+"号了吧 )
通过修改$PS4的值,就可以达到sh –x 时显示行号还有函数名称的目的了
二、shell 调试
set
-n
sh -n <shellscript> 用于脚本的语法检查
-x
sh -x <shellscript> 用于脚本的跟踪方式,显示所执行的每一条命令
-c
sh -c 'a=1;b=2;let c=$a+$b;echo "c=$c"' "string" 从strings中读取命令
-e
在"set -e"之后出现的代码,一旦出现了返回值非零,整个脚本就会立即退出
可见设置"set -e",在脚本开发过程中可能很有帮助,而在开发完成后,特别是对于后期可能有升级的脚本,则可能是埋下了安全隐患
三、常犯错误
1 ssh 远程执行后台命令不靠谱
【脚本内容】:
ssh hostname "cat bin &"
【执行】
[work@www.baidu.com bin]$ ssh localhost "cat bin &"
cat: bin: Is a directory
[work@www.baidu.com bin]$ echo $?
2 文件泄露
【脚本内容】:
local status=$( mySsh ${remote_host} "{ ${command%%;}; }&>/tmp/$$ && echo 0 || echo 1" )
【问题】:上述代码将远程执行命令行的输出结果导入到一个以pid命名的临时文件中,在脚本关闭的时候没有清除,每一次执行将创建一个新文件,很可能导致文件泄露问题。
3 命令连接
问题一:
【脚本内容】:
cd to_del; rm -rf *
【问题】:如果cd 目录失败,rm -rf * 会错误地删除当前目录下的所有文件
【解决】使用 && 连接 cd失败将不会继续执行后面的命令
问题二:
【脚本内容】:
for data in ${datalist{@}}
do
runRemoteCmd ${host} "cd ${data_path}.new && [[ -f ${data_flag} ]]" || suc=0 && break
done
【问题】:这里的 || && 是同一个优先级
那么就是说 && 后面的语句 break无论什么情况下都不可能被执行到
4 变量传播
【脚本内容】:
func(){
for((i=0;i<$RETRY_TIMES;i++))
do
NOTICE "delBlacklist"
done
}
for (( i=0; i<pggroup_size; i++))
do
func()
done
【问题】:“i”的值自增之后会传递到外层调用脚本,导致外层调用脚本的循环跳过或死循环
【解决】避免使用i,j,k等常见的循环控制变量,使用自定义的变量名,如retry_count等
在shell函数中定义的变量加上local关键字
5 字符串判断
【脚本内容】:
if [ "$1" = "continue" ] then
echo “succ”
fi
【问题】:$1为空,打印“succ”
【sh -x 执行】:succ
【原因】: $1为空会造成语法错误,返回0,继续执行if代码块中的逻辑,导致判断错误
【解决】修改成 if [ "a$1" = "acontinue" ] // 这个问题我经常犯
6 判断一个数组是否为空:
【脚本内容】:
if [ -z ${pg_readyDatalist[@]} ]
then
…………
fi
【问题】:不可如此判断,超过一个元素时,语法错误
【sh -x 执行】:
+ '[' -z model gtrindex ']'
retrbs_restart.sh: line 366: [: model: binary operator expected
【原因】:
-z 只能判断一个变量是否为空
判断一个list是否为空
【解决】判断list元素个数是否为0
例如: if [ ${#ps_retrbs[@]} -eq 0 ]
7 If语句判断
【脚本内容】:
if [ -f ./$i]
then
echo "test"
fi
【问题】: .$i] 的“]”前面没有空格,造成语法错误
【sh -x 执行】:./test.sh: line 3: [: missing \`]
【原因】: If语句的条件判断“[ ]”,“[”之后和“]”之前必须有空格
【解决】加上空格
if 的三种条件表达式
if
command
then
if
函数
then
命令执行成功,等于返回0 (比如grep ,找到匹配)
执行失败,返回非0 (grep,没找到匹配)
if [ expression_r_r_r ]
then
表达式结果为真,则返回0,if把0值引向then
if test expression_r_r_r
then
表达式结果为假,则返回非0,if把非0值引向then
# 测试拷贝是否成功
if cp $1 $2 > /dev/null 2>&1
# successful, great do nothing
then :
else
# oh dear, show the user what files they were.
echo "`basename $0`: ERROR failed to copy $1 to $2"
exit 1
fi
第三问 数组的操作你真的会吗?
数组声明
declare -a array_name # 声明数组,也可以不声明 declare -a nums=(1 2 3 4) # 声明数组,同时也可以赋值 unset array_name # 删除数组 unset num[0] # 删除数组中的某个元素
不像JAVA/C等强编程语言,在赋值前必须声明;SHELL只是弱编程语言,可事先声明也可不声明
数组定义
方式1 一般而言,str="a sdf af 23s" 这样的变量只是将 $A 替换为一个单一的字符串 array_name=(1 2 3 4) 则是定义为组数 方式2 names[0]=J names[1]=A names[2]=D 方式3 names=([0]=J [1]=A [2]=D [3]=W) 方式4 str="a sdf af 23s" names=($str) 当然可以用命令的方式产生数组(结果是需要带空格的字符串) curstat=(`stat -c "%s %i" $filename`) ,curstat 是一个数组变量
1) 数组中的元素,必须以"空格"来隔开,这是其基本要求;
2) 定义数组其索引,可以不按顺序来定义,比如说:names=([0]=Jerry [1]=Alice [2]=David [8]=Wendy);
3)字符串是SHELL中最重要的数据类型,其也可通过($str)来转成数组,操作起来非常方便;
数组长度
数组全部提取,得到J A D W(全部组数)
nums=(J A D W)
方式1
${#nums[@]}
方式2
${#nums[*]}
求元素长度
nums=(J A D W)
方式1
${#nums[0]) # 求第一个元素的长度
方式2
expr length ${nums[0]} # 用expr length函数来求元素的长度
方式3
echo ${nums[0]}|wc -L
方式4
echo ${nums[0]} | awk -F "" '{print NF}' # 先分隔,再用NF域来求元素长度
方式5
echo ${nums[0]} | awk '{print length($0)}' # 使用awk中的length()函数
1) 使用${array_name[@]} 或者 ${array_name[*]} 都可以全部显示数组中的元素
2) 同样道理${#array_name[@]} 或者 ${#array_name[*]}都可以用来求数组的长度
3)求数组中元素的长度方法有很多,相当于求字符串的长度
数组遍历
names=(J A D W)
方式1 按索引来遍历
for((i=0;i<${#names[*]};i++))
do
echo ${names[$i]}
done
方式2 不按索引来遍历
index=0
for i in ${names[@]}
do
echo "第${index}个元素的值为==> ${i}"
let index++
done
数组切片
names=(J A D W)
$names # 默认取第一个元素,输出J
${names[0]} # 取索引为零对应的元素,输出J
${names[@]} # 取数组中全部元素
${names[@]:1} # 从索引为1到后面所有的值, 输出: A D W
${names[@]:0:2} # 从索引为0开始取2位, 输出: J A
${names[@]::3} # 从索引为0开始取3位, 输出: J A D
${names[@]:(-2):2} # 从倒数第二个元素开始,取2位, 输出: D W
new_names=${names[@]:1:3} # 得到新的切片数组
元素切片
names=(Jerry Alice David Wendy)
${names[0]:0} # 索引为0取所有,输出 Jerry
${names[0]:1} # 索引为1开始取所有, erry
${names[0]:2:2} # 索引为2开始取2位, rr
${names[0]:1} # 超出元素长度显示空行
1) 通用的格式${array[@]:起始位置:长度},中间以":"隔开,如果第二项省略的话,就取后面所有的项
2) 切片后返回的是字符串,可以通过 新数组=(${旧数组[@]:索引:长度})来索引,参见上面最后一个例子
3) 区别于Python之一:起始位置可以为负数,但必须以放在()中,长度不能为负数
4) 区别于Python之二:第二项在Python里面是结束索引,在Shell则代表所取元素的长度
5) 区别于Python之三:Python可以通过 list[-1:-4:-2]来反向取数,在Shell则实现不了
数组替换
array=(one two three four)
${array[@]/e/E} 最小匹配替换,每个元素只替换一次,输出 onE two thrEe four
${array[@]//e/E} 最大匹配替换,每个元素可替换多次,输出 onE two thrEE four
${array[@]/e/} 最小匹配删除,只删除一个符合规定的元素,输出on two thre four
${array[@]//e/} 最大匹配删除,可删除多个符合规定的元素,on two thr four
${array[@]/#o/E} 从左往右匹配替换,只替换每个元素最左边的字符,Ene two three four
${array[@]/%o/E} 从右往左匹配替换,只替换每个元素最右边的字符,one twE three four
${array[@]#t*e} 每个元素,从左向右进行最短匹配 one two e four
${array[@]##t*e} 每个元素,从左向右进行最长匹配 one two four
${array[@]%t*e} 每个元素,从右向左进行最短匹配 one two four
${array[@]%%t*e} 每个元素,从右向左进行最长匹配
数组堆栈
array=(1 2 3 4)
模拟push操作
(${array[@]} 5) 结果 1 2 3 4 5
模拟pop操作
(${array[@]:0:$((${#array[@]}-1))})
使用实例
function get_todelete_days()
{
# declare -A DAY_ARR
# DAY_ARR=""
for i in $(seq 1 10);
do
THIS_DAY=$(date -d "$(($KEEP_DAYS+$i)) day ago" +%Y.%m.%d)
DAY_ARR=( "${DAY_ARR[@]}" $THIS_DAY) # 数组追加
done
echo ${DAY_ARR[*]}
}
# 返回数组的写法
TO_DELETE_DAYS=(`get_todelete_days`)
第四问:是问 echo 知多少?
echo -n command 并没任何的 argument ,那结果就只剩一个换行符号了 -e :启用反斜线控制字符的转换(参考下表) -E:关闭反斜线控制字符的转换(预设如此) -n :取消行末之换行符号(与 -e 选项下的 \c 字符同意) \a:ALERT / BELL (从系统喇叭送出铃声) \b:BACKSPACE ,也就是向左删除键 \c:取消行末之换行符号 \E:ESCAPE,跳脱键 \f:FORMFEED,换页字符 \n:NEWLINE,换行字符 \r:RETURN,回车键 \t:TAB,表格跳位键 \v:VERTICAL TAB,垂直表格跳位键 \n:ASCII 八进位编码(以 x 开首为十六进制) \\:反斜线本身 # 变量输出,否则就直接转义为普通字符串了 echo -e "a\tb\tc\nd\te\tf"
sudo echo x >’ 时’Permission denied’ 处理办法?
1. 利用 “sh -c” 命令,它可以让 bash 将一个字串作为完整的命令来执行,这样就可以将 sudo 的影响范围扩展到整条命令。 具体用法如下: sudo sh -c "echo a > 1.txt" 利用bash -c 也是一样的,现在bash shell 流行。 2. 利用管道和 tee 命令,该命令可以从标准输入中读入信息并将其写入标准输出或文件中, 具体用法如下: echo a |sudo tee 1.txt echo a |sudo tee -a 1.txt // -a 是追加的意思,等同于 >>
第五问 " "(双引号) 与 ' '(单引号)差在哪?
单引号里面是转义的
双引号里面是不转义的
ENV="live"
salt-ssh -i goldcoin-1-15 state.sls script.test "pillar={env:[$ENV]}"
* literal:也就是普通纯文字,对 shell 来说没特殊功能。
* meta:对 shell 来说,具有特定功能的特殊保留字符。
* IFS:由 <space> 或 <tab> 或 <enter> 三者之一组成(我们常用 space
* CR:由 <enter> 产生。
* hard quote:' ' (单引号),凡在 hard quote 中的所有 meta 均被关闭。
* soft quote: " " (双引号),在 soft quoe 中大部份 meta 都会被关闭,但某些则保留(如 $ ) 比如 "$a"
* escape : \ (反斜线),只有紧接在 escape (跳脱字符)之后的单一 meta 才被关闭。
$ A=B\ C \ 是转义了 $ echo '"$A"' # 最外面的是单引号 ,meta被关闭了 "$A" $ echo "'$A'" # 最外面的是双引号 , 'B C'
$ awk {print $0} 1.txt
由于 { } 在 shell 中并没关闭,那 shell 就将 {print $0} 视为 command block ,
但同时又没有" ; "符号作命令区隔,因此就出现 awk 的语法错误结果。
A=0
$ echo "das"|awk '{print $A,'"$A"'}'
das 0
$ echo "das"|awk \{print\ $A,\$$A\}
第六问 var=value?export 前后差在哪?
如下是一些变量设定时常见的错误:
A= B :不能有 IFS ,
1A=B :不能以数字开头
$A=B :名称不能有 $
a=B :这跟 a=b 是不同的
如下则是可以接受的设定:
A=" B" :IFS 被关闭了 (请参考前面的 quoting 章节)
A1=B :并非以数字开头
A=$B :$ 可用在变量值内
This_Is_A_Long_Name=b :可用 _ 连接较长的名称或值,且大小写有别。
*变量替换(substitution)*
A=BCD
A=${A}E 与 A=$AE 是不一样的
第七问 exec 跟 source 差在哪?
多个脚本嵌套使用!
* 所谓 source 就是让 script 在当前 shell 内执行、而不是产生一个 sub-shell 来执行。
* exec 也是让 script 在同一个行程上执行,但是原有行程则被结束了。
$ ./1.sh source
PID for 1.sh before exec/source/fork:20051
1.sh: $A is B
using source...
PID for 2.sh: 20051 #继承了主进程 . ./2.sh ;;
2.sh get $A=B from 1.sh
2.sh: $A is C
PID for 1.sh after exec/source/fork:20051
1.sh: $A is C
$ ./1.sh fork
PID for 1.sh before exec/source/fork:20067
1.sh: $A is B
using fork by default...
PID for 2.sh: 20068 #重新派生出来进程 ./2.sh ;;
2.sh get $A=B from 1.sh
2.sh: $A is C
PID for 1.sh after exec/source/fork:20067
1.sh: $A is B
$ ./1.sh exec
PID for 1.sh before exec/source/fork:20083
1.sh: $A is B
using exec...
PID for 2.sh: 20083
2.sh get $A=B from 1.sh
2.sh: $A is C #原来的1.sh已经被干掉了 exec ./2.sh ;;
第八问:( ) 与 { } 差在哪?
( ) 将 command group 置于 sub-shell 去执行,也称 nested sub-shell
{ } 则是在同一个 shell 内完成,也称为 non-named command group
比如:
先判断 -f 然后 执行括号内的
[ ! -f "$3" ] && ( echo "not of warfile" && exit 2 )
[ ! -d "$1" ] && ( color_message "warn" "[WARN] not of path" && exit 1 )
如何定义一个 函数:
方法1
function function_name {
command1
command2
command3
....
}
方法2
fuction_name () {
command1
command2
command3
....
}
区别就是若碰到所定意的名称与现有的命令或别名(Alias)冲突的话,方式二可能会失败
将其他script中用source加载并执行
/etc/rc.d/init.d/function
第九问:$(( )) 与 $( ) 还有${ } 差在哪?
在 bash shell 中,$( ) 与 ` ` (反引号) 都是用来做命令替换用(command substitution)的。
完成引号里的命令行,然后将其结果替换出来,再重组命令行。
$ echo the last sunday is $(date -d "last sunday" +%Y-%m-%d)
the last sunday is 2014-06-08
2, 在多层次的复合替换中,` ` 须要额外的跳脱( \` )处理,而 $( ) 则比较直观。
用:
command1 $(command2 $(command3))
代替:
command1 `command2 \`command3\``
一个好的函数调用执行的写法:
echo 'the number of parameter in "$@" is ' $(my "$@")
可以将参数传入到函数中
file=/dir1/dir2/dir3/my.file.txt
${file#*/}:拿掉第一条 / 及其左边的字符串:dir1/dir2/dir3/my.file.txt
${file##*/}:拿掉最后一条 / 及其左边的字符串:my.file.txt
${file#*.}:拿掉第一个 . 及其左边的字符串:file.txt
${file##*.}:拿掉最后一个 . 及其左边的字符串:txt
${file%/*}:拿掉最后条 / 及其右边的字符串:/dir1/dir2/dir3
${file%%/*}:拿掉第一条 / 及其右边的字符串:(空值)
${file%.*}:拿掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file
${file%%.*}:拿掉第一个 . 及其右边的字符串:/dir1/dir2/dir3/my
${file%%my.file.txt}: 删除要除去的字符: /dir1/dir2/dir3/
${file/file.txt/123/}: 把file.txt替换为123
# 是去掉左边(在鉴盘上 # 在 $ 之左边)
% 是去掉右边(在鉴盘上 % 在 $ 之右边)
切片
${file:0:5}:提取最左边的 5 个字节:/dir1 ,可以代替awk中的substr ! #注意!!!!
${file:5:5}:提取第 5 个字节右边的连续 5 个字节:/dir2
${file:(-8):8} 取最后8个字符,注意要用()扩起来
我们也可以对变量值里的字符串作替换:
${file/dir/path}:将第一个 dir 提换为 path:/path1/dir2/dir3/my.file.txt
${file//dir/path}:将全部 dir 提换为 path:/path1/path2/path3/my.file.txt
补充:
#echo $(dirname $(pwd)) --目录
/data
# echo $(basename $(pwd)) --文件
mybackup
#如果file没有赋值,就执行'du'
file='df'
${file-'du'}
# ${file-'du'}
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/cciss/c0d0p1 5036284 384092 4396360 9% /
/dev/cciss/c0d0p2 50394996 23604008 24231032 50% /data1
/dev/cciss/c0d0p10 24169412 12608964 10332684 55% /data2
none 4153996 0 4153996 0% /dev/shm
/dev/cciss/c0d0p3 20161204 13244604 5892460 70% /home
/dev/cciss/c0d0p9 5036284 3200700 1579752 67% /opt
/dev/cciss/c0d0p8 5036284 201788 4578664 5% /tmp
/dev/cciss/c0d0p6 10080488 6669944 2898476 70% /usr
/dev/cciss/c0d0p7 5036284 385788 4394664 9% /var
file 有变量的时候,执行file;file没有变量的时候执行'-'后面的 du
# zgt='124234'
计算变量长度
# echo ${#zgt}
6
expr length string
echo "abc" |awk -F "" '{print NF}'
echo “Alex”|awk '{print length($0)}'
# echo ${A[3]}
23s
A[3]=23s 则是将第四个组数重新定义为 23s ...
$(( )) 的用途:它是用来作整数运算的。
$ echo $((923/8))
115
a=5; ((a++)) 可将 $a 重定义为 6
a=5; ((a--)) 则为 a=4
a=5; b=7; ((a < b)) 会得到 0 (true) 的返回值。
利用 ${ } 还可针对不同的变量状态赋值(没设定、空值、非空值):
${file-my.file.txt} 假如 $file 没有设定,则使用 my.file.txt 作传回值。(空值及非空值时不作处理)
${file:-my.file.txt} 假如 $file 没有设定或为空值,则使用 my.file.txt 作传回值。 (非空值时不作处理)
${file+my.file.txt} 假如 $file 设为空值或非空值,均使用 my.file.txt 作传回值。(没设定时不作处理)
${file:+my.file.txt} 若 $file 为非空值,则使用 my.file.txt 作传回值。 (没设定及空值时不作处理)
${file=my.file.txt} 若 $file 没设定,则使用 my.file.txt 作传回值,同时将 $file 赋值为my.file.txt 。 (空值及非空值时不作处理)
${file:=my.file.txt} 若 $file 没设定或为空值,则使用 my.file.txt 作传回值,同时将 $file赋值为 my.file.txt 。 (非空值时不作处理)
${file?my.file.txt} 若 $file 没设定,则将 my.file.txt 输出至 STDERR。 (空值及非空值时不作处理)
${file:?my.file.txt} 若 $file 没设定或为空值,则将 my.file.txt 输出至 STDERR。 (非空值时不作处理)
9.2 字符串转化为数组
a="one,two,three,four"
OLD_IFS="$IFS" //变量$IFS存储着分隔符,这里我们将其设为逗号 "," OLD_IFS用于备份默认的分隔符,使用完后将之恢复默认。
IFS=","
arr=($a)
IFS="$OLD_IFS"
for s in ${arr[@]}
do
echo "$s"
done
>>>
one
two
three
four
第十问: $@ 与 $* 差在哪?
脚本中的输入参数
./my.sh one two three four five six seven eigth nine ten
第10个参数,可不是$10 ,$10的意思是 $1+0 ,如果要表示$10就需要用 ${10}
$# 是否带参数进来,0就是不带参数进来
$# 传递到脚本的参数个数 $0 脚本名 $1 arg1
$* 以一个单字符串显示所有向脚本传递的参数。与位置变量不同,此选项参数可超过9个
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的进程ID号
$@ 与$#相同,但是使用时加引号,并在引号中返回每个参数
$- 显示shell使用的当前选项,与set命令功能相同
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
"$@" 则可得到 "p1" "p2 p3" "p4" 这三个不同的词段(word)﹔可以选择各个值
"$*" 则可得到 "p1 p2 p3 p4" 这一整串单一的词段,不能分开选择
第十一问:&& 与 || 差在哪?
连接符号 ,(请务必注意 [ ] 之间的空格键﹗)
&& 与 || 都是用来"组建"多个 command line 用的:
* command1 && command2 :其意思是 command2 只有在 RV 为 0 (true) 的条件下执行。
* command1 || command2 :其意思是 command2 只有在 RV 为非 0 (false) 的条件下执行。
其次,bash 的 test 目前支持的测试对像只有三种[注意里面都要加""的!]:
* string:字符串,也就是纯文字。
[ "$A" = 123 ]:是字符串的测试,以测试 $A 是否为 1、2、3 这三个连续的"文字"
* integer:整数( 0 或正整数,不含负数或小数点)
[ "$A" -eq 123 ]:是整数的测试,以测试 $A 是否等于"一百二十三"
* file:文件。
[ -e "$A" ]:是关于文件的测试,以测试 123 这份"文件"是否存在
[ -d "$file" -a -x "$file" ]
是表示当 $file 是一个目录、且同时具有 x 权限时,test 才会为 true 。
[[ ]] 双大括号,用于正则判断
u="12323" 用于判断u是不是数字打头
[[ "$u" =~ ^[0-9] ]] && echo "true"
整数的比较
-eq 数值相等。
-ne 数值不相等。
-gt 第一个数大于第二个数。
-lt 第一个数小于第二个数。
-le 第一个数小于等于第二个数。
-ge 第一个数大于等于第二个数。
使用两重小括号
示例:
[ $count -lt 10 ] #有变量的比较
[ $(ls -l $file|awk '{print $5}') -gt 10000 ] #含有执行command的比较
[[ 0 == $? ]] #在含有通配符的比较中需要使用[[]],注意!
(( 45 > 200 )) && echo "da" #比较整数大小
小数的比较
用 awk 做比较
字符串比较
#注意要用""将变量扩起来,否则变量不存在或者是空串的话造成语法错误
= 等于,如:if [ "$a" = "$b" ]
== 等于,如:if [ "$a" == "$b" ],与=等价
注意:==的功能在[[]]和[]中的行为是不同的,如下:
s=kkl
$ [[ "$s" == "k"* ]] && echo "oko"
oko
!= 字符串不等不等于,如:if [ "$a" != "$b" ]
<> 大于小于比较符,在ASCII字母顺序下.如:
if [[ "$a" < "$b" ]]
if [ "$a" \< "$b" ]
注意:在[]结构中"<"需要被转义.
-z 空串,字符串为"null".就是长度为0.
-n 非空串,字符串不为"null" ,可以当作测试该变量是否存在:[ -n $f ] && echo "dasd"
注意:
使用-n在[]结构中测试必须要用""把变量引起来.使用一个未被""的字符串来使用! -z
以工作,但这是不安全的.习惯于使用""来测试字符串是一种好习
-s 非空文件
[ -s "MyStock.conf" ] && echo "oko"
-f 文件判断
统一第一个 判断符号不能用 ||,
[ -f "$2" ] || ( color_message "warn" "[WARN] not of warfile" ) && exit 2
chown -R mysql.mysql $MYSQL_DATA || log_exit "Error: chown -R mysql.mysql $MYSQL_DATA, failed"
[ ! -d "$HD_DIR" ] && echo "not of HD_DIR" && exit 1
示例:
[ "$ans" == "y" ]||[ "$ans" == "Y" ]
[ "$a"x == x ] #字符串否存在,可以用${a-test} 代替判断并输出
[ ! -f $file ] #文件是否存在
[ -z $ORACLE_HOME] #测试变量是否为空,注意空 null unsite的区别
[ -n "$uid" ] #是否是空字段 如空则返回 0
until [ -z "$1" ] # Until all parameters used up
连接符
用来代替 [ -s "yixin-guan.war" ] && [ -f "yixin-guan.war" ] && [ -r "yixin-guan.war" ]
[ -s "yixin-guan.war" -o -f "yixin-guan.war" ] 多重判断
-a 逻辑与,操作符两边均为真,结果为真,否则为假。
-o 逻辑或,操作符两边一边为真,结果为真,否则为假。
! 逻辑否,条件为假,结果为真。
shell计算
1 $echo $(expr 24 + 4) 注意里面空格不可少 只能计算整数
28
2 $let h=24+4 只能计算整数
echo $h
28
3 $ echo 23.5 23.2|awk '{print $1*$2}' 能计算小数,还是这个最方便
echo 4.3 35.2|awk '{if($1>$2)print 1;else print 0}'
545.2
4 $ echo "scale=3;23.5*243.2/(22-2)"|bc -q 表达式虽然老,但是能计算小数,但是还要指定scale
285.760
第十二问:> 与 < 差在哪?
stdin stdout stderr
0< stdin
1> stdout 等于 >
2> stderr
$ ls my.file no.such.file 2>file.err
my.file
$ ls my.file no.such.file 2>/dev/null #空设备
my.file
这样就只有stdout了,因为 stderr被写到file.err 里面了,这样可以控制输出
* 2>&1 就是将 stderr 并进 stdout 作输出
* 1>&2 或 >&2 就是将 stdout 并进 stderr 作输出
crontab扩展
0~59 表示分
1~23 表示小时
1~31 表示日
1~12 表示月份
0~6 表示星期(其中0表示星期日)
*/10 * * * * sh /root/memory_monitor 每隔10分钟
0 */6 * * * 每6小时一次
0 * * * * * 每小时的0分钟执行命令
0 23 * * * /usr/sbin/ntpdate 134.98.5.80
执行权限:
chown root:root /usr/bin/crontab
chmod u+s,g+s /usr/bin/crontab
$ set -o noclobber #避免> 输入,取消只需要+o
$ echo "dasd" > yx_id.txt
-bash: yx_id.txt: cannot overwrite existing file
cat < file > file
#由于 ,> file 会先将 file 清空,然后才读进 < file,变成空文件,比如awk处理文件等等,
pipe line 管道符
第十三问: if 还是 case ?
举例:
checkRunning(){
if [ -f "$ACTIVEMQ_PIDFILE" ]; then
if [ -z "`cat $ACTIVEMQ_PIDFILE`" ];then
echo "ERROR: Pidfile '$ACTIVEMQ_PIDFILE' exists but contains no pid"
return 2
fi
ACTIVEMQ_PID="`cat ${ACTIVEMQ_PIDFILE}`"
RET="`ps -p "${ACTIVEMQ_PID}"|grep java`"
if [ -n "$RET" ];then
return 0;
else
return 1;
fi
else
return 1;
fi
}
if ( checkRunging ); then
echo "okoo"
fi
comd1 && {
comd2
comd3
} || {
comd4
comd5
}
等于:
if comd1
then
comd2
comd3
else
comd4
comd5
fi
多级判断条件
if [ expreession 1 ]; then
elif [ expreession 2 ]; then
elif [ expreession 3 ]; then
else
fi
(若 then 后不想跑任何 command ,可用" : " 这个 null command 代替
如果是多值的情况,需要用到case代替if else,
举例:
case "$YN" in
[Yy]|[Yy][Ee][Ss])
text
;;
case "$1" in
start)
start
;;
stop)
stop
;;
第十四章 for while 与 until 差在哪?
bash shell 中常用的 loop 有如下三种:
* for
* while
* until
14.1 for 多种格式
# 读取文件,追加字符串
for i in `cat list.num`;do A="$A|$i";done
# 把文件都列出来
for f in *.txt
for i in {1..20}
for i in $(seq 10)
for var in one two three four five
for ((i=0;i<30;++i))
# 循环定位文件 candidates=" /opt/software/jdk/bin/java /etc/alternatives/java /usr/lib/jvm/java-1.8.0/bin/java /usr/lib/jvm/jre-1.8.0/bin/java /usr/lib/jvm/java-1.7.0/bin/java /usr/lib/jvm/jre-1.7.0/bin/java /usr/lib/jvm/java-11.0/bin/java /usr/lib/jvm/jre-11.0/bin/java /usr/lib/jvm/java-11-openjdk-amd64 /usr/bin/java " for candidate in $candidates do [ -x "$JENKINS_JAVA_CMD" ] && break JENKINS_JAVA_CMD="$candidate" done
14.2 while
while [ "$num" -le 10 ]; do
echo "num is $num"
((num++)) == num=$(($num + 1)) #注意用法 (()) ,而 $(()) 作为函数是被调用的,它的左边应该还要表达式
done
ls $filename > slog.tmp #while 逐行读取信息
cat slog.tmp |while read line
do
do something
done
while举例
REG_DATE_MATH=$(LC_ALL=en_US.UTF-8; i=0; while [ 1 ]; do M="$M"$(date -d"-$i day" "+%Y-%b-%d"); i=$(($i +1)); if [ $i -le 6 ]; then M="$M|"; else break; fi; done; echo -e $M)
* break 强制结束循环
* continue 跳出本次,进行下一个循环体,是 return一个值,这样可以不执行下面的,用return也可以
* exit 是结束 script/shell
第十五章 正则匹配
regeular
匹配字符串
ls -d 2015-08-11_14-26-{01,30,09} 2> /dev/null
2015-08-11_14-26-01 2015-08-11_14-26-30
匹配单个
ls -d 2015-08-11_14-26-[03]*
匹配一个段
ls -d 2015-08-11_14-26-[0-3]*
a*b
a?b 只有一个字符
a[xyz]b 只要是 x y z
a{abc,xyz,123}b 只有这三个
a[!0-9]b 只要不是0-9内, [! ] 中的 ! 只有放在第一顺位时, 才有排除之功,注意与 ^ 的区别,!只能放在[]内
中的- 左右两边均有字符时, 才表示一段范围, 否则仅作 "-"(减号) 字符来处理
/tmp/*[-z]/[a-zA-Z]* 以-或者z结尾的子目录下以英文字母开首的路径名称
第十七问 find知多少
现在时间 4-25 14:30
0 现在时间到之前24小时,4-25 14:30 到 4-24 14:30
+0 4-25 14:30 到 之前时间
1 4-24 14:30 到 4-25 14:30
+1 4-24 14:30 到 之前时间
find . -name "*.txt" -ctime 1 -type f -print|xargs tar -zcvf a.tar.gz
find . -name "datafile" -ctime +10 -type d |xargs rm -rf #这种命令要非常小心,有根目录被删除的陷阱
find 目录 -type f -print0|xargs -0 grep -l 内容关键字|xargs -i rm -f {}
find / -user tom -exec cp -ra {} /root/tom \;
如果find多个文件, 需要用引号括起来.
find . -ctime +10 -exec mv {} trace \;
--统计所有文件的行数
find calllog/history/ -name "*.log" |xargs cat|wc -l
--找到文件然后置空的几种方法
find . -name "zgt"|awk '{a="echo";b=$0;system(a" >"b)}'
find . -mtime +1 -type f| awk '{print "echo > "$0}'|bash
find . -mtime +1 -exec cp /dev/null {} \;
find . -name "zgt" |xargs sed -i '1,$d'
find . -name "zgt" -exec sed -i '1,$d' {} \;
--查找
find -type f -exec sh -c 'file="{}";type=$(file $file);[[ $type =~"ASCII text" ]] && echo $file' \;
--找到文件,并追加打包
for i in `cat list1`
do
find . -name "$i"|xargs tar -rvf getfile.tar
done
--查询文件并累加大小
find -name "*.sql" -exec ls -l {} \;|awk '{sum+=$5}END{print sum}' sum=0
查询两天以内的文件(排除目录 .)
find /app2/yiliao-app/log/app-server/app.info.log* -type f -mtime -2|xargs grep -E \"$NUM\"
[语法]: find 路径名… 表达式
[说明]: find 命令递归地遍历指定路径下的每个文件和子目录,看该文件是否能使表达式值为真,以下 n 代表一个十进制整数,+n 代表大于 n , -n 代表小于 n ,下面是合法表达式说明:
-amin n
查找系统中最后N分钟访问的文件
-atime n
查找系统中最后n*24小时访问的文件
-cmin n
查找系统中最后N分钟被改变文件状态的文件
-ctime n
查找系统中最后n*24小时被改变文件状态的文件
-mmin n
查找系统中最后N分钟被改变文件数据的文件
-mtime n
查找系统中最后n*24小时被改变文件数据的文件
-name 模式 文件名与模式匹配则为真,(\ 为转意符)
-perm [-]八进制数 文件存取模式与八进制数相同则为真若有- 选项,则文件存 find . -type f -perm -111
部分匹配,因为执行位的代码是1,所以所有具有执行权限的文件都将被匹配,不管是777,751,701, ! 取反
find . -name "*2013*" -perm -000|xargs rm -f
-maxdepth 0 仅在本目录
-size n[c] 文件块长度为 n 则真(一块为512字节),若 +1M 以上
有c 选项,则文件字节长度为 n 则真
-atime n 若文件的最近访问时间为 n 天前则为真,
find 命令将改变其访问的目录的访问时间
-mtime n 若文件的最近修改时间为 n 天前则为真
-ctime n 若文件状态为 n 天前改变则为真
文件的 Access time,atime 是在读取文件或者执行文件时更改的。
文件的 Modified time,mtime 是在写入文件时随文件内容的更改而更改的。
文件的 Create time,ctime 是在写入文件、更改所有者、权限或链接设置时随 Inode 的内容更改而更改的。
-exec 命令 { }\; 若命令返回值为0则真,{ }内为命令参数,
此命令必须以 \; 为结束
-ok 命令 { }\; 与 exec 相同,只是在命令执行前先提示,若
回答 y 则执行命令
-print 显示输出使表达式为真的文件名
-newer 文件 若文件的访问时间比newer 指定的文件新则真
-depth 先下降到搜索目录的子目录,然后才至其自身
-mount 仅查找包含指定目录的文件系统
-local 文件在当前文件系统时为真
-type c 文件类型为 c 则真,c 取值可为 b(块文件) c (字符文件)
d(目录) l (符号链接) p (命名管道) f (普通文件)
\( 表达式 \) 表达式为真则真
-links n 文件链接数为 n 时为真
-user 用户 当文件属于用户时为真,用户可用数字表示UID
-nouser 当文件不属于 /etc/passwd 中的一个用户时为真
-group 文件组 当文件属于文件组时为真,文件组可用数字表示GID
-nogroup 当文件不属于 /etc/group 中的一个组时为真
-fstype 类型 当文件所属文件系统类型为指定类型时真
-inum n 当文件 i 节点号为 n 时为真
-prune 当目录名与模式匹配时,不再搜索其子目录
可以用逻辑操作符将简单表达式连接成复杂表达式
逻辑操作符有 ! 表示非操作, -o 表示或操作,两个表达式并列则表示
与操作
[例子]:
find / -name find* -print
从根目录开始搜索文件名如 find* 的文件并显示之
find ./ -exec sleep{1}\; -print
每秒显示一个当前目录下的文件
find $HOME \(-name a.out -o -name ‘*.o’ \) -atime +7 -exec rm {} \;
从$HOME目录开始搜索,删除所有文件名为a.out 或 *.o 且访问时间在7天前的文件(注意 -o 多了 需要转义!)
第十八问 sort 和 uniq 怎么用
cat HNAllpass |awk '{if($2>1) print} '|sort -k2nr > Sort
sort -k1.11n 根据第一列的 第11个字符[数字]排序
sort -k1n -k3nr 两列排列
多位排序
cat 123 |sort -k1n -k2.1rn -k2.3rn -k2.5rn -k2.7rn -k2.8rn|more
104 3.1.0.03
104 3.1.0.02
104 3.1.0.01
sort -t\| -k1,3
sort -t: -k1 qq.tel 跳过 : 符号,进行排序
uniq -w 9 //对每行第N 个字符以后的内容不作对照
cat haha.txt |tr -d '自贡市教育系统' |sort -k4n|awk '{if($6!~"2014") print $0}'|awk '{print $4}'
du -sh ~/* |sort -k 1 -h -r
根据大小排序
用来排序文件大小 du -sh *|sort -h
第十九问 exit 和 return 差在哪里?
注意: return 是一个 0-255 之间的值!
在 shell command line 中可用 $? 這個變量得到最"新"的一個 return value ,也就是剛結束的那個行程傳回的值。
Return Value(RV) 的取值為 0-255 之間,由程式(或 script)的作者自行定議:
return 返回一个值,比如返回3 而根据 $? 所得到的是 3
if 中的函数表达式
function test()
{
local t=$1
if [ "$t" -gt 0 ];then
return 3;
fi
return 0;
}
if
test $1 && [ 8 -eq 8 ] // 注意,分行写就是 || 的用意
then
echo "oko"
fi
第二十问 真实的应用
function check_prot()
{
declare -A PORT
PORT['PAAS']="80 8088"
[[ $MOD =~ ^(PAAS|PAASAGENT|RABBITMQ)$ ]]|| log_exit "Error"
for port in ${PORT[$MOD]}
do
lsof -i :$port -P && log_exit "Exist $port"
done
}
传输输入
while getopts 'hu:p:' opt; do
case $opt in
h)
usage
exit 1
;;
u) ADMIN_NAME=${OPTARG}
;;
p) ADMIN_PWD=${OPTARG}
;;
?)
usage
exit 1
;;
esac
done

浙公网安备 33010602011771号