# Shell 编程

## Shell 语法

* 变量：字符串、数字、环境变量、参数
* 条件判断: shell 中的布尔值
* 程序控制：if\elif\for\while...
* 命令列表
* 函数
* 内置命令
* 获取命令的执行结果

### 变量

```
foo=bar
```

* 区分大小写
* 等号两边无空格
* 所有变量都是字符串

```
foo=bar
echo $foo

foo="another bar"
echo $foo

bar=7+3
echo $bar
```

**环境变量**

与系统相关的一些全局变量，常用的有：

```
$HOME    当前用户的家目录
$PATH     搜索命令列表，以冒号分隔
$0    shell 脚本文件名
$#    传递给脚本的参数个数
$$     shell 脚本进程号（PID），可用于生成唯一的临时文件
$PS1     命令提示符
$PS2    二级命令提示符，一般为 >
$IFS     输入域分隔符，通常为空格、制表符、换行符
```

**参数变量**

与参数相关的变量：

```
$1，$2，... $n    第n个参数

$* 以IFS分隔的所有参数

$@ 以空格分隔的所有参数

$#    参数个数
```

### 条件判断: shell 中的布尔值

**test 、 \[ 命令，用于执行常见判断**

例：检查一个文件是否存在

```
if test -f foo.php
then
    ...
fi

if [ -f foo.php ]
then
    ...
fi
```

**test 字符串比较**

* string1 = string2&#x20;
* string1 != string2
* -n string 字符串不为空则为真
* -z string 字符串为 null（空） 则为真

**test 算术比较**

* expression1 -eq expression2 两式相等则为真
* -ne 不等
* -gt 大于
* -ge 大约等于
* -lt 小于
* -le 小于等于
* ! expression 非&#x20;

**test 文件测试**

* -d file 如果文件是一个目录则为真
* -f 文件为普通文件则为真
* -e 文件存在则为真
* -s 文件大小不为0则为真
* -r 文件可读则为真
* -w 文件可写则为真&#x20;
* -x 文件可执行则为真

示例:

写一个判断 /etc/hosts 文件是否可写的程序，如果可写，打印“yes”，否则打印 “no”

```
#!/bin/sh
if [ -w /etc/hosts ]
then
    echo "yes"
else
    echo "no"
fi
exit 0
```

创建一个变量，读取用户输入，如果用户输入yes，则显示“yes”，输入no 则显示 “no”，否则显示“Sorry”

```
#!/bin/sh
echo -n "your answer: "
read yourvar
if [ "$yourvar" = "yes" ]
then
    echo "yes"
elif [ "$yourvar" = "no" ]
then
    echo "no"
else
    echo "Sorry"
fi
exit 0
```

### for循环

基本格式:

```
for variable in values
do
    ...
done
```

例：

```
for foo in bar fud 555
do
    echo $foo
done
exit 0
```

例：列出 f开头，.sh 结尾的文件

```
for file in $(ls f*.sh)
do
    echo $file
done
exit 0
```

**$(command) 用于执行command命令并获取其输出结果**

其他示例

```
#for i in $(ls)
#for i in `ls`
#for i in {d..z}
for ((i=0;i<5;i++))
do
    echo $i
done
```

计算累加

```
count=4
for((i=1;i<=$count;i++))
do
    total=$((total+i))
done
echo "total=$total"
```

### while 循环

基本格式

```
while condition 
do
    ...
done
```

例：

```
read trythis
while [ "$trythis" != "secret" ]
do
    echo "Sorry, try again: "
    read trythis
done
exit 0
```

### until 循环——反复执行直到条件变为真

基本格式

```
until condition 
do
    ...
done
```

例：

```
until who | grep "$1" > /dev/null
do
    sleep 60
done
echo -e '\a'
exit 0
```

### case 语句

基本格式:

```
case variable in 
    pattern [ | pattern ] ...) statements ;;
    pattern [ | pattern ] ...) statements ;;
    ...
esac
```

### 命令列表

AND 列表，左边statement执行成功才执行右边，全部执行成功才算成功

```
statement1 && statement2 && statement3 && ...
```

OR 列表，从左到右执行一系列命令，直到某条执行成功就算成功，后面的不再被执行

```
statement1 || statement2 || statement3 || ...
```

### 语句块

在某些只允许使用单个语句的地方（比如AND、OR列表中），要使用多条语句，则可以使用{}构造一个语句块

```
get_confirm && {
    ls > $tmp_file
    cat $tmp_file > $tracks_file
}
```

### 函数

基本格式:

```
function_name () {
    statements
}
```

function\_name 必须在调用函数前对它定义。

1. 数调用时，脚本的位置参数（$\* $@ $# $1 $2 $...） 会被替换为函数的参数，函数执行完毕后，会恢复原值
2. 函数中的变量默认为全局作用域，除非使用 local 关键字定义
3. 通过 return 命令可以让函数返回数字值,常用于表示函数执行是否成功。如需返回字符串值，则需在函数中使用 echo，然后再函数外使用 $() 捕获；或者将字符串保存在一个变量中，函数执行完毕后读取该变量。
4. 如果函数没有使用 return 指定返回值，则函数返回值为最后一条命令的退出码

**捕获函数输出：**

```
foo() { echo Hi; }

result="$(foo)"
```

定义局部变量：

```
test="gloabal var"
foo() {
    local text="local var"
    echo "function foo is executing"
    echo $text
}
echo "script starting"
echo $text
foo
echo "script ended"
echo $text
```

结果

```
script starting
global var
Function foo is executing
local var
script ended
global var
```

### 命令

1. break 跳出一层循环，continue 继续下一轮循环
2. : （冒号）空命令，有时用于简化条件逻辑，相当于 true 的别名。如 while : 为无限循环
3. . (点，约等于 source 外部命令)，通常情况下，脚本执行一条外部命令时会启动一个新的子shell，命令在这个新环境中执行，结束后此环境被丢弃，给父shell返回退出码。但.命令可用于在当前环境中执行外部命令或脚本程序。类似 PHP 的 include（）
4. echo -e 可启用反斜线转义，参考 help echo； -n 不输出换行。

```
echo -e "\e[1;32m Si\e[0m\e[1;45mjiao\e[0m\e[1;34mmao\e[0m"
```

1. eval ，允许执行动态生成的代码。例：&#x20;

```
foo=10;  x=foo;  eval y='$'$x;  echo $y
```

1. exec 中断当前shell，执行 exec 所指定的命令
2. exit n 以退出码n结束运行。0为正常;1—125可用于自定义；126 文件不可执行；127 命令未找到；
3. export 导出环境变量，后面执行的子进程均可见；
4. expr ，执行数学运算，如 x=$(expr $x + 1)，也可写为 $((...)) ，具体参考 man expr
5. printf 格式化打印
6. return n 使函数返回，无参数则返回最后一条命令退出码
7. set 为shell设置参数变量；如 `set $(date); echo $2` 可输出date命令的第二列（月）
8. shift ，把所有参数变量左移一个位置，使 $2 变为 $1, $3 变为 $2
9. unset ,从当前环境中删除变量或函数

### 命令的执行

$(command) 或 \`command\` ，捕获command命令执行的字符串输出结果（不是退出码）；

### 算术扩展

$((expression))，类似 expr命令，但比它快

### 字符串处理

* \##尽量多找，#尽量少找，找到后去掉前面找过的部分
* %,类似#，但是从后往前找
* : 开始位置：截取长度
* \# 字符串长度
* \:- 如果file变量为空，则返回 default\_file，否则返回 file 变量的值
* := 如果file变量为空，则设置file=default\_file并返回，否则返回 file 变量的值
* :+ 如果file变量不为空，则返回default\_file

```
filename=ni.hao.avi
echo ${filename##*.}
echo ${filename#*.}
echo ${filename%%.*}
echo ${filename:2:4}
echo ${#filename}
echo ${filename1:-$filename}
echo ${filename1:=$filename}
echo ${filename1:+$count}
```

结果


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://phper.shujuwajue.com/linux/shell-bian-cheng.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
