在Linux平台,在遇到问题时解决可能要求理解项目的相关脚本,这就需要理解和掌握shell语法;掌握shell语法,也是自动化构建系统实现的必备知识。对于完整的shell语法,是不可能一篇文章讲完,甚至一本书也远远不够。shell是一个作为用户与Linux系统间接口的程序,它允许用户向操作系统输入需要执行的命令。这不仅是一套指令语法,还包含扩展的程序和命令功能,只能依靠日常使用中积累掌握。
这里以常用的脚本语法进行总结, 目录如下所示。
在Linux日常使用中,大都是直接通过命令来完成文件夹创建,文件下载,处理,解压,删除等工作。不过在维护Linux平台时,这类指令操作往往多且繁杂,为了统一处理这类问题,Linux下的shell环境支持一整套shell语法来管理操作,类似windows下的bat和powershell语法,可以通过脚本来管理日常任务,这里列出我在使用Linux中涉及的语法。
在命令行中,使用双引号和单引号表示引用一个整体,另外单引号可以屏蔽转译字符。
注意:本文中的shell语法在/bin/bash环境下验证,对于嵌入式Linux环境可能因为使用不同的shell工具,并不一定支持全部的语法,需要注意。
可通过”$[name]”的形式获取环境变量的具体值,对于环境变量通过printenv可以查看当前用户下的环境变量。
变量名 | 描述 |
---|---|
$UID | 当前账号的UID号 |
$USER | 当前账号的用户名称 |
$HISTSIZE | 当前终端的最大历史命令数目 |
$HOME | 当前账户的根目录 |
$LANG | 当前环境使用的语言 |
$PATH | 命令搜索目录 |
$PWD | 当前工作目录 |
$RANDOM | 随机返回0到32767的树 |
$PS1 | 命令提示符, 给出当前的用户名,机器名和当前目录名以及$符合 |
$PS2 | 二级命令提示符, 用于提示后续的输入,通常是>字符 |
Linux平台命令支持与和或执行,分别如下。
# and语句执行,满足前一个后一个才执行
statement1 && statement2 && ... && statementX
# or语句执行,前一个不满足后一个才执行
statement1 || statement2 || ... || statementX
# 例程
# 跳转uboot目录,不成功则返回
cd "${PLATFORM_UBOOT_PATH}"/ || return
# 对于 && 和 ||,可以配合{}来构造语句块。
# 测试构造语句块输出
ls -alF && {
echo "block test"
ps -ef | grep ls
}
对于shell文件支持注释,主要包含块注释和单行注释。
# 块注释,<<后可以使用任何字符串,但结尾字符串需要保持一致
<<comment
这是块注释
comment
# 单行注释
函数是封装可以被调用的命令行表达式,具体格式如下。
function [name]()
{
#函数体
}
对于函数参数,通过如下格式访问。
下面举例说明
function show_info()
{
i=1
echo "var num:$#"
echo "var str:$*"
while [ $i -le $# ]; do
echo "${!i}"
let i++
done
}
show_info 4 5 6 7 8 9
符号包含: 双引号(“”),单引号(‘’),反引号(``),转义符号(‘')。
在linux系统中,可以直接使用var=xxx的形式声明变量,然后通过$var, ${var}进行读取。
CMD
效果相同,结果为该命令的输出。path=/home/freedom
#输出path的值
echo $path
echo ${path}
#"",''都表示一个整体,''会屏蔽特殊符号
echo "$test" #输出test变量
echo '$test' #输出$test字符串
echo "当前账户: `who | awk '{print $1}'`"
echo "当前账户: $(who | awk '{print $1}')"
#定义变量只读,后续不能够修改
url="file"
readonly usl
#删除变量
uset url
shell中支持数组变量,如下定义。
#定义数组array0和array1
array0=(A B C D)
array1[0] = A
array2[1] = B
#访问关联数组的元素${array[index]}格式
${array0[0]}
#获取数组内的所有元素
echo ${array0[*]}
echo ${array0[@]}
#计算fiber
num=12
fibo=(1,1)
for ((i=2;i<$num;i++))
do
let fibo[$i]=fibo[$i-1]+fibo[$i-2]
done
echo $fibo
shell支持关联数组,类似字典
#定义关联数组
declare -A site0=(["google"]="www.google.com" ["taobao"]="www.taobao.com")
declare -A site1
site1["google"]="www.google.com"
site1["taobao"]]="www.taobao.com"
#访问关联数组内的元素
echo ${site0["google"]}
#获取关联数组内的所有元素
echo ${site0[*]}
echo ${site0[@]}
#获取关联数组的所有键值
echo ${!site0[*]}
echo ${!site0[@]}
#获取关联数组的长度
echo ${#site0[*]}
echo ${#site0[@]}
在bash中,可以通过$((statement))进行数学运算
#通过$(())进行数学运算
val=$((2*3+6))
val0=$((2*5+(6-2)*3))
echo $val
echo $val0
#通过$[]进行数学运算
var1=100
var2=45
var3=$[ ${var1} / ${var2} ]
echo $var3
#通过bc命令进行运算
var4=$(echo " scale=4; 3.44/5" | bc)
echo ${var4}
#可以使用`command`格式执行命令
NUM=1
echo `expr $NUM + 1`
let x=1
let x++
echo $x
在bash中, 默认会将错误输出到屏幕中,此时使用输出重定向,可以将异常数据保存到FALSA中。
#将date的输出写入testfile
date>testfile
#将date的输出重定向追加到testfile后
date>>testfile
#将标准输出和错误输出定向到不同文件
#标准输出重定向不带数字
#错误输出重定向使用格式:"cmd 2>err.file"
ls -l /etc/hosts /nofile > test.log 2> err.log
#将标准输出和错误输出定向到同一文件(&>不追加,&>>追加)
ls -l /etc/hosts /nofile &> err2.log
#将错误处理导向标准输入通道,从而写入标准输出文件
ls nofile > 1.txt 2>&1
#将信息导入到/dev/null中
ls nofile &> /dev/null
如果反过来希望文件被命令所访问,则使用输入重定向”<, «“。
命令 « 分隔符 …… 分隔符
#将testfile用wc处理
wc<testfile
cat > test.file << EOF
this is a test!
this is the next lines!
EOF
Linux的管道,可以将命令的输出结果,存储到管道中,然后让后续命令从管道读取数据,进行进一步处理,对于管道,通过”|“连接。如果命令需要顺序执行,可以通过先写入文件,在读取文件访问,当然也可以优化为管道使用。使用管道命令流时不要重复使用相同的文件名(可能在开始时就被覆盖)。
命令1 | 命令2
模型为: 命令1输出 => 写入管道 => 管道 => 读取管道 => 命令2输入
# 基于文件中转的命令
ls -alF>>testfile
sort<testfile
# 使用管道的连续命令
ls -alF | sort
# 显示当前进程,排序,然后分页显示
ps | sort | more
# 读取file1, 排序内容,删除重复后写入mydata.txt
cat file1.txt | sort | uniq > mydata.txt
在linux中,循环语句主要有for和while语句,其中for用于迭代列表中的项或者一系列数字。在循环中支持使用break中止当前循环,continue则中止某次循环,但不结束整个循环。在循环中支持语句来跳出循环或者占位。
for语句结构如下所示。
for variable in values
do
statements
done
for例程显示如下所示。
# for循环格式
for variable in list; do
# 执行循环体的命令
done
# 遍历/etc/profile.d/的sh文件
for i in /etc/profile.d/*.sh; do
if [ -r $i ]; then
echo $i
fi
done
unset i
# 执行ls脚本
for file in $(ls f*.sh); do
lpr $file
done
# 遍历数组
fruits=("苹果" "香蕉" "柚子")
for fruit in "${fruits[@]}"; do
echo "i love ${fruit}"
if [ ${fruit} == "香蕉" ]; then
break
fi
done
# 遍历读取的数据
for entry in $(cat /etc/passwd); do
echo ${entry}
done
用于循环的while语句,当不满足条件时,循环结束。
# while循环格式
while condition; do
statements
done
# 遍历数据
count=1
while [ ${count} -le 3 ]; do
echo ${count}
count=$((count+1))
done
# 数学运算
i=1
sum=0
while [$i -le 10]; do
let sum = sum + $i
let i++
done
echo $i $sum
for ((i=1; i<10; i++))
do
echo $i
done
# 遍历文件
# 指定文件夹路径
folder="/home/program/download/tmp"
# 遍历文件夹中的文件
while IFS= read -rd '' file; do
echo "处理文件: $file"
# 在这里可以执行你需要的操作,对每个文件进行处理
done < <(find "$folder" -type f -print0)
用于循环的until语句,当满足条件时,循环结束。
# until循环语句
until condition
do
statement
done
# 判断用户是否登录
until who | grep "$1" > /dev/null
do
sleep 60
done
常见的分支语句有if和case,其中if可以用于判断,如检测某个脚本,库或目录不存在,执行其它分支,其格式如下.
# if [ command ]; then
# dothings
# elif [ command ]; then -- 可省略
# dothings
# else
# dothings
# fi
其中command主要包含如下部分.
#文件或者目录判断
[ -a FILE ] 如果文件存在则为真
[ -b FILE ] 文件存在,且为块设备文件
[ -c FILE ] 文件存在,且为字符设备文件
[ -d FILE ] 如果文件存在且是一个目录则返回为真
[ -e FILE ] 如果指定的文件或目录存在时返回为真
[ -f FILE ] 如果文件存在且是一个普通文件则返回为真
[ -L FILE ] 判断文件存在且为软链接设备
[ -p FILE ] 判断文件存在且为命名管道
[ -r FILE ] 如果文件存在且是可读的则返回
[ -s FILE ] 文件存在且大小非空
[ -w FILE ] 如果文件存在且是可写的则返回为真
[ -x FILE ] 如果文件存在且是可执行的则返回为真
[ file1 -ef file2 ] 文件使用相同设备,inode编号则为真
[ file1 -nt file2 ] file1比file2更新,或仅file2不存在时为真
[ file1 -ot file2 ] file1比file2更旧,或仅file2不存在时为真
# 字符串判断
[ -z STRING ] 如果STRING的长度为零则返回为真,即空是真
[ -n STRING ] 如果STRING的长度非零则返回为真,即非空是真
[ STRING1 ] 如果字符串不为空则返回为真,与-n类似
[ STRING1 == STRING2 ] 如果两个字符串相同则返回为真
[ STRING1 != STRING2 ] 如果字符串不相同则返回为真
[ STRING1 < STRING2 ] 如果 “STRING1”字典排序在“STRING2”前面则返回为真。
[ STRING1 > STRING2 ] 如果 “STRING1”字典排序在“STRING2”后面则返回为真
# 数值判断
[ INT1 -eq INT2 ] INT1和INT2两数相等返回为真
[ INT1 -ne INT2 ] INT1和INT2两数不等返回为真
[ INT1 -gt INT2 ] INT1大于INT2返回为真
[ INT1 -ge INT2 ] INT1大于等于INT2返回为真
[ INT1 -lt INT2 ] INT1小于INT2返回为真
[ INT1 -le INT2 ] INT1小于等于INT2返回为真
#逻辑判断
[ ! EXPR ] 逻辑非,如果 EXPR 是false则返回为真。
[ EXPR1 -a EXPR2 ] 逻辑与,如果 EXPR1 and EXPR2 全真则返回为真。
[ EXPR1 -o EXPR2 ] 逻辑或,如果 EXPR1 或者 EXPR2 为真则返回为真。
[ EXPR ] || [ EXPR ] 用OR来合并两个条件
[ EXPR ] && [ EXPR ] 用AND来合并两个条件
基于上述命令配合,下面展示一些常用的命令组合。
# 检测目录是否在PATH中
echo $PATH
if [ -d "$1" ] && echo $PATH | grep -E -q "(^|:)$1($|:)"; then
echo "directory $1 in PATH"
else
echo "directory $1 not in PATH"
fi
# 可以使用if test condition, 等同于 if [ condition ]
if test -f "file.c"; then
echo "file exist!"
fi
# 使用if...elif..else结构
read -p "timeofday" timeofday
if [ "$timeofday" == "0" ]; then
echo "morning"
elif [ "$timeofday" == "1" ]; then
echo "afternoon"
else
echo "sorry"
exit 1
fi
exit 0
case则主要用于分支判断语句,根据不同的内容执行不同的分支,格式为
# 分支语句格式
case variable in
pattern1)
command1
;;
pattern2)
command2
;;
#......
esac
# 执行不同的分支处理
case "$BUILD_OPT" in
"u-boot")
compile_uboot
;;
"kernel")
compile_kernel
;;
"rootfs")
compile_kernel
create_rootfs
;;
"image")
compile_uboot
compile_kernel
create_rootfs
do_pack
;;
*)
echo "sorry, input not valid"
;;
esac
shell脚本输入变量通过$n(n=1,2…)获取输入值
# $1, $2表示文件输入值
# 如 ./test.sh test1 test2
# 则 $1=test1, $2=test2
echo "read 1 value is $1"
echo "read 2 value is $2"
读取命令行输入的值使用read命令。
选项 | 功能 |
---|---|
-p | 显示提示信息 |
-t | 设置读入数据的超时时间 |
-n | 设置读取n个字符后结束,默认会读取标准输入的一整行内容 |
-r | 支持读取\, 而默认read命令会将\理解为转义字符 |
-s | 静默模式,不显示输入内容 |
-t | 设置超时时间(单位s),超时自动退出 |
#读取外部数据,存储到value中
read -p "read:" value;
#超时等待5s输入用户名
read -t 5 -p "输入用户名:" user
read -s -p "输入密码:" passwd
useradd "$user"
echo $passwd | passwd --stdin "$user"
在shell脚本中,用$?表示执行的状态,可以用exit命令返回脚本执行的结果。
#打印执行状态
if [[ $? -ne 0 ]]; then
read -p "shell faild, wheather exit?(y/n, default is y)" exit_val
if [ -z ${exit_val} ] || [ ${exit_val} == 'y' ]; then
exit 1
fi
fi
直接开始下一章节:嵌入式Linux平台软件的交叉编译。