blog:shell:01_shell_summary

Shell使用总结

使用两个函数, inifetch和iniwrite

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[@]})"这种方式,才能将整个数组作为一个参数传递

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

  2. 使用printf

    $ printf "%x" 127 $ 7f $ printf "%d" 0x1f $ 31

替换指定目录下所有文件中的字符串

例:将所有文件中的 RESBMPMLVOLUMELIGHT 替换为 RESBMPMLSILENTMODELIGHT

find ./newcdr -type f | xargs sed -i 's/RESBMPMLVOLUMELIGHT/RESBMPMLSILENTMODELIGHT/g'

调试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个字符。

例: 提取文件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()
{
    echo "catch signal"
}
# when the signal 2 (SIGINT) is received, call catch_signal
trap "catch_signal" 2
  • blog/shell/01_shell_summary.txt
  • 最后更改: 2022/08/25 16:58
  • caodan