# Shell使用总结 ## 处理ini格式的配置文件 使用两个函数, ini_fetch和ini_write ``` function ini_fetch() { if [ $# -ne 3 ]; then return fi local file=$1 local section=$2 local key=$3 item=`sed -n '/^\['"$section"'\]/,/^\[/ {/^\['"$section"'\]/b; /^\[/b; /^'"$key"'\s*=.*/ p;}' $file` if [ "${item}x" != "x" ]; then value=${item#*=} echo $value fi } function ini_write() { if [ $# -ne 4 ]; then echo 1 fi local file=$1 local section=$2 local key=$3 local value=$4 sed -i '/^\['"$section"'\]/,/^\[/ {/^\['"$section"'\]/b; /^\[/b; s/^'"$key"'\s*=.*/'"$key"'='"$value"'/;}' $file echo $? } ``` 例如配置文件如下: ``` [firmware_version] version=v1.1 RC1 [support_screen_size] count=3 screen_size0=320X240 screen_size1=480X272 screen_size2=800X480 ``` >$ ini_fetch ./lunch-cdr.ini "support_screen_size" "screen_size0" >320X240 ## 数组的用法 定义数组: `ARRAY_NAME=()` 元素之间用空格作为间隔,并且用大括号括起来 获取数组中的所有成员: `${ARRAY_NAME[@]}` 或者 `${ARRAY_NAME[*]}` 添加一个成员: `ARRAY_NAME=(${ARRAY_NAME[@]} $new_combo)` 获取数组的长度: ``` ${#ARRAY_NAME[@]} ``` 取出数组中的第i个成员的值: `${ARRAY_NAME[$i]}` 如何将数组作为参数传递给函数? 由于shell数组的功能很弱,无法直接作为参数传递,可以采用如下的方式转换: 例: ``` function test_array() { local i local array1=(`echo $1 | cut -d " " --output-delimiter=" " -f 1-`) local array2=(`echo $2 | cut -d " " --output-delimiter=" " -f 1-`) for i in `seq 0 $((${#array1[@]} - 1))` do echo "array1 element $i: ${array1[$i]}" done for i in `seq 0 $((${#array2[@]} - 1))` do echo "array1 element $i: ${array2[$i]}" done } array1=(test1 test2 test3 test4) array2=(colin1 colin2 colin3 colin4 colin5) test_array "$(echo ${array1[@]})" "$(echo ${array2[@]})" 运行结果如下: $ ./array.sh array1 element 0: test1 array1 element 1: test2 array1 element 2: test3 array1 element 3: test4 array1 element 0: colin1 array1 element 1: colin2 array1 element 2: colin3 array1 element 3: colin4 array1 element 4: colin5 ``` 注意: 传递的时候一定要使用`"$(echo ${array1[@]})"`这种方式,才能将整个数组作为一个参数传递 ## 使用脚本来管理ckermit USB转串口设备在linux下的设备名为 /dev/ttyUSB* ckermit是一个linux下的串口工具, 需要配合配置文件~/.kermrc使用 ~/.kermrc的配置如下 ``` set line /dev/ttyUSB1 set speed 115200 set carrier-watch off set handshake none set flow-control none robust set file type bin set file name lit set rec pack 1000 set send pack 1000 set window 5 set session-log TIMESTAMPED-TEXT ``` 每次插拔USB转串口线,设备名都有可能变化,为了省去每次都要修改配置文件的步骤 写了如下的一个脚本来封装ckermit命令 https://github.com/caodan4linux/myserial ``` cat myserial ######################################################################### # File Name: myserial # Author: Dan.Cao # mail: caodan2519@gmail.com # Created Time: 2014年12月23日 星期二 11时57分14秒 # # Description: # manage the ckermit, connect to the serial port # sudo permission my be needed by ckermit # # Date Author Comment # 2014/12/23 Dan.Cao first version # 2020/06/06 Dan.Cao generate ckermit configuration file # support input baudrate ######################################################################### #!/bin/sh str=`ls /dev/ttyUSB* 2> /dev/null` if [ "${str}x" = "x" ]; then echo "not find the serial port!" exit 1 fi serial_ports=(`echo $str | cut -d " " --output-delimiter=" " -f 1-`) serial_cnt=${#serial_ports[@]} echo "find $serial_cnt serial ports:" for i in `seq 0 $((serial_cnt - 1))` do echo -e "\t [$i]: \t $serial_ports[$i]" done echo -n "please select the port number:" read choice echo "choice is $choice" #1. $1是脚本的第一个参数,这里作为awk命令的第一个参数传入给awk命令。 #2. 由于没有输入文件作为输入流,因此这里只是在BEGIN块中完成。 #3. 在awk中ARGV数组表示awk命令的参数数组,ARGV[0]表示命令本身,ARGV[1]表示第一个参数。 #4. match是awk的内置函数,返回值为匹配的正则表达式在字符串中(ARGV[1])的起始位置,没有找到返回0。 #5. 正则表达式的写法已经保证了匹配的字符串一定是十进制的正整数,如需要浮点数或负数,仅需修改正则即可。 #6. awk执行完成后将结果返回给isdigit变量,并作为其初始化值。 #7. isdigit=`echo $1 | awk '{ if (match($1, "^[0-9]+$") != 0) print "true"; else print "false" }' ` #8. 上面的写法也能实现该功能,但是由于有多个进程参与,因此效率低于下面的写法。 isdigit=`awk 'BEGIN { if (match(ARGV[1],"^[0-9]+$") != 0) print "true"; else print "false" }' $choice` if [[ $isdigit != "true" ]]; then echo "please input a digit number" exit 1 fi if [ $choice -ge $serial_cnt ]; then echo "input number must less than $serial_cnt" exit 1 fi serial_port=${serial_ports[$choice]} echo "select port is $serial_port" # select Baudrate read -p "Enter the baudrate (115200): " baudrate if [ "${baudrate}x" == "x" ]; then baudrate=115200 fi echo "baudrate=${baudrate}" ##################################################################### # generate configurations #serial_port=dd # 本来sed替换的分隔符是 '/', 但是会和路径变量serial_port中的'/'冲突, # 所以使用冒号':' 代替之前的 '/' 分隔符 #sed -i 's:set line .*$:set line '"$serial_port"':' ~/.kermrc CONFIG_FILE="/tmp/kermrc.${USER}" cat > ${CONFIG_FILE} << GEN_KERMRC set line ${serial_port} set speed ${baudrate} set carrier-watch off set handshake none set flow-control none robust set file type bin set file name lit set rec pack 1000 set send pack 1000 set window 5 set session-log TIMESTAMPED-TEXT c GEN_KERMRC # start ckermit ${CONFIG_FILE} ``` ## 进制转换 1. 使用括号表达式转换 ``` $ num=15 $ echo $((16#${num})) $ 21 $ num=015 $ echo $((8#${num})) $ 13 ``` 井号前面的数字表示进制 2. 使用括号表达式2 ``` $ ((num=0x15)) $ echo $num $ 21 $ ((num=015)) $ echo $num $ 13 ``` 使用这种方式,对数字格式的要求: 16进制的数以0x开头, 8进制的数以0开头 3. 使用bc计算器实现 ``` $ echo "obase=10; ibase=16; 12" | bc $ 18 ``` 4. 使用printf ``` $ printf "%x" 127 $ 7f $ printf "%d" 0x1f $ 31 ``` ## 全局替换 替换指定目录下所有文件中的字符串 例:将所有文件中的 RES_BMP_ML_VOLUME_LIGHT 替换为 RES_BMP_ML_SILENTMODE_LIGHT find ./newcdr -type f | xargs sed -i 's/RES_BMP_ML_VOLUME_LIGHT/RES_BMP_ML_SILENTMODE_LIGHT/g' ## 调试脚本 set -x与set +x 调试shell脚本不需要什么特殊工具,bash本身就包含了一些选项,能够打印除脚本接受的参数和输入 使用选项-x, 启动跟踪调试shell脚本: `$bash -x script.sh` -x 将脚本中执行过的每一行都输出到stdout, 控制方式如下: set -x 在执行时显示参数和命令 set +x 禁止调试 set -v 当命令进行读取时显示输入 set +v 禁止打印输入 例1: ``` #!/bin/sh set -x echo "I see!" mkdir sour ``` ``` $ ./test.sh ++ echo 'I see!' I see! ++ mkdir sour mkdir: 无法创建目录"sour": 文件已存在 ``` 例2: ``` #!/bin/sh for i in `seq 0 6` do set -x echo $i set +x done ``` 例3: 通过传递环境变量_DEBUG来控制调试信息 ``` #!/bin/sh function DEBUG() { [ "$_DEBUG" == "on" ] && $@ || : } for i in `seq 0 6` do DEBUG echo "i is $i" done ``` 执行结果: ``` $ ./test.sh $ _DEBUG=on ./test.sh i is 0 i is 1 i is 2 i is 3 i is 4 i is 5 i is 6 ``` 运行时如果没有设置_DEBUG=on,则不会看到echo输出的信息,是因为DEBUG函数中的命令 `:` 告诉shell不要进行任何操作 ## ${} # % 例: ``` path=dir1/dir2/dir3/dir4/filename.txt/file.txt echo "path is: $path" echo "path is: ${path#*/}" #删除 第一个/ 及其左边的字符 echo "path is: ${path##*/}" #删除 最后一个/ 及其左边的字符 echo "path is: ${path#*.}" #删除 第一个. 及其左边的字符 echo "path is: ${path##*.}" #删除 最后一个. 及其左边的字符 echo "path is: ${path%/*}" #删除 最后一个/ 及其右边的字符 echo "path is: ${path%%/*}" #删除 第一个/ 及其右边的字符 echo "path is: ${path%.*}" #删除 最后一个. 及其右边的字符 echo "path is: ${path%%.*}" #删除 第一个. 及其右边的字符 输出结果如下: path is: dir1/dir2/dir3/dir4/filename.txt/file.txt path is: dir2/dir3/dir4/filename.txt/file.txt path is: file.txt path is: txt/file.txt path is: txt path is: dir1/dir2/dir3/dir4/filename.txt path is: dir1 path is: dir1/dir2/dir3/dir4/filename.txt/file path is: dir1/dir2/dir3/dir4/filename ``` 记忆方法: 在键盘上的位置: `#`位于`$`的左边, `%`位于`$`的右边. 所以使用#则表示从匹配字符左边开始删除, 使用%则表示从匹配字符右边开始删除 `#*/` 表示 匹配第一个`/`及其左边的字符 `##*/` 表示 匹配最后一个`/`及其左边的位置 `%/*` 表示 匹配最后一个`/`及其右边的位置 `%%/*` 表示 匹配第一个`/`及其右边的位置 其中`*`表示通配符,`/`表示希望匹配的字符是斜杠,也可指定其他的字符。 ## 提取字符串中指定位置的字符 `${str:n:m}` 例: ``` $ str=string:xxx $ start=${str:0:7} $ echo $start 输出结果为: string: ${str:0:7}就是提取str中从第0个字符开始的7个字符。 ``` ## 提取二进制文件中指定offset处的值 例: 提取文件test.bin中的第12个字节开始的4个字节的内容 以16进制的格式表示: ``` $ od -A none -j 12 -N 4 -t x4 test.bin 310ad3cf ``` 以10进制的格式表示: ``` $ od -A none -j 12 -N 4 -t d4 test.bin 822793167 ``` option N指定需要dump的bytes数 option j表示跳过开始的多少bytes option t用于设置dump的格式 option 用于设置每行一的地址前缀格式,none表示不显示地址前缀 ## catch signal ``` catch_signal() { echo "catch signal" } # when the signal 2 (SIGINT) is received, call catch_signal trap "catch_signal" 2 ```