How to Write Shell Scripts
The basic grammer of shell/Bash script.
ref: shell tutorial
How to Write Shell Script
首先做个澄清:Shell 是一类程序的统称,其中最流行的是 Bash Shell,所有 Linux 发行版和 MacOS 都安装了它;除此之外,zsh 也很流行。考虑到 Bash 的流行程度以及其他更复杂的 Shell 的功能基本是 Bash 的超集,因此我们这里只介绍 Bash Shell 脚本。
前置知识:Shell 是如何执行命令的
Shell 在执行命令时,通常先 fork 出一个子进程,再由子进程 exec 目标程序。
这里有两个常见例外:
- Shell 内建(built-in)命令,例如
cd、export,通常直接在当前 Shell 进程中执行。(但不包括ls,ls不是内建命令) - 使用
source执行脚本,例如source any-script.sh,脚本内容会直接在当前 Shell 中执行。
Shell 中的变量可以分为普通 Shell 变量和环境变量。普通变量不会被子进程继承(或者说复制);环境变量会被复制给子进程。这也意味着子进程修改自己的环境变量不会影响父进程。
定义方式如下:
# 普通变量 |
前置知识:执行shell脚本的具体方式
执行脚本文件:
直接指定
sh来执行该脚本,不需要shebang,也不需要脚本有执行权限(因为该脚本直接作为参数传给了sh).sh script_file_path
需要写shebang来指定解释器, 并且要指定脚本路径.
必须加上
./使得该command name被识别为一个路径名. 否则shell会继续在alias, builtin和PATH中搜索该command name. 1chmod +x script_file ##(chown, chgrp optionally)
./script_file使用
.或source在当前shell session中执行该脚本source script_file
or
. script_file
因此, 该方法可以用于刷新当前shell环境:
source ~/.bashrc
Shell script是能在命令行直接输入的, 但仅会作用一次
注意: 方法1,2都是新开一个子shell session,在其中执行脚本,而方法3, 4是在当前shell session中执行脚本
Environment virable
$PATH详见Shell Script Searching Path
常见的环境变量:
| 环境变量 | 说明 |
|---|---|
| $HOME | 当前用户的登陆目录 |
| $PATH | 以冒号分隔的, 由多个路径所组成的, 用来搜索命令的列表 |
| $PS1 | 命令行提示符,通常是”$”字符 (很多主题都会改掉$PS1) |
| $PS2 | 辅助提示符,用来提示后续输入,通常是”>”字符 |
| $IFS | 输入区分隔符。当shell读取输入数据时会把一组字符看成是单词之间的分隔符,通常是空格、制表符、换行符等 |
我们在当前Shell进程中指定了var1变量 |
例子:用 source 执行会更改环境变量的脚本
假设有一个脚本 reset_env.sh:
export FOO=new_value |
如果直接执行:
export FOO=old_value |
输出仍然是:
old_value |
因为 reset_env.sh 在子进程中执行,修改的是子进程自己的环境。
如果改成:
source reset_env.sh |
输出则是:
new_value |
因为 source 会在当前 Shell 中执行脚本,所以能够直接修改当前 Shell 的环境变量。
Parameter variable
$0- Name of the script$1to$9- Arguments to the script.$1is the first argument and so on.- 当
n>=10时,需要使用${n}来获取参数
- 当
$@- 全部参数组成的列表$#- Number of arguments$?- Return code of the previous command$*: 全部参数连接成的字符串,按$IFS的第一个字符分割$$- Process identification number (PID) for the current script!!- Entire last command, including arguments. A common pattern is to execute a command only for it to fail due to missing permissions; you can quickly re-execute the command with sudo by doingsudo !!$_- Last argument from the last command. If you are in an interactive shell, you can also quickly get this value by typingEscfollowed by.orAlt+.
Variable
assign variables in bash:
foo=bar- Note that
foo = barwill not work since it is interpreted as calling thefooprogram with arguments=andbar. In general, in shell scripts the space character will perform argument splitting.- 因此不要加空格
- Note that
access the value of the variable:
$foo- 等价于
${foo}, 花括号可以精确地界定变量名称的范围。
- 等价于
可以用
read命令从标准输入接受数据并赋值:read valexample:
! /usr/bin/env bash
echo -n "Enter your name:"
read name
echo "hello $name"
exit 0
String
Strings in bash can be defined with
'and"delimiters, but they are not equivalent.Strings delimited with
'are literal strings and will not substitute variable values whereas"delimited strings will.foo=bar
echo "$foo"
prints bar
echo '$foo'
prints $foo
Array
array initialization −
array_name=(value1 ... valuen)
assign
array_name[index]=value
access Array Values
{array_name[index]}
Quoting Mechanism
metacharacters
Unix Shell provides various metacharacters which have special meaning while using them in any Shell Script and causes termination of a word unless quoted.
* ? [ ] ' " \ $ ; & ( ) | ^ < > new-line space tab |
quoting
The following table lists the four forms of quoting −
| Sr.No. | Quoting & Description |
|---|---|
| 1 | Single quote All special characters between these quotes lose their special meaning. |
| 2 | Double quote Most special characters between these quotes lose their special meaning with these exceptions −$`$'"\ |
| 3 | Backslash Any character immediately following the backslash loses its special meaning. |
| 4 | Back quote (aka backtick) Everything you type between backticks is evaluated (executed) by the shell before the main command, and the output of that execution is used by that command, |
The Single Quotes: 其内容不转义, 相当于在每个字符前加 backslash
The Double Quotes: 其内容转义
The Backslash: 取消其后面的一个字符的转义
The Backquotes:将其内容视作 command 并执行, 与后文的CMD substitution类似
var=`command`
DATE=`date`
echo $DATE
等价于后文的CMD substitution
echo $(DATE)
常见语法
条件语句
- As with most programming languages, bash supports control flow techniques including
if,case,whileandfor.
if
syntax:
if [ expression ] |
if [ expression 1 ] |
紧凑形式: ; (同一行上多个命令的分隔符)
example:
if [ -f ~/.bashrc ]; then |
!/bin/sh |
case
syntax:
case word in |
example:
!/bin/sh |
!/bin/sh |
select in && case in
select in语句自带循环
select variable in value_list |
- variable: 表示变量
- value_list: 取值列表
- in: Shell关键字
select in 通常和 case in 一起使用,在用户输入不同的编号时可以做出不同的反应
example:
!/bin/sh |
该命令的while版本:
while [ "$item" != "Finish" ]; |
循环语句
break: 从for/while/until循环退出
for
syntax:
for var in word1 word2 ... wordN |
example:
for FILE in $HOME/.bash* |
for f in *.png |
while
syntax:
while condition |
example:
quit=n |
a=0 |
命令组合
分号串联:
command1 ; command2 ; ... |
条件组合:
statement1 && statement2 && statement3 && ... |
statement1 || statement2 || statement3 || |
语句块
{ |
或
{ |
test expression 或 [ expression ]
[(akatest) command the and[[ ... ]]test construct are used to evaluate expressions[是一条命令, 与test等价,大多数shell都支持。在现代的大多数sh实现中,[与test是builtin命令[]将其operand直接当作argument
[[,是关键字,许多shell(如ash bsh)并不支持这种方式[[]]将其operand进行参数引用,算术扩展和CMD substitution, 不需要手动转义等
Function
syntax
function_name(){ |
example:
mcd () { |
return
exit: 不仅会退出函数, 还会退出执行该函数的shellreturn code:仅仅退出函数. 和command的return code同
return code
- Commands have Return Code to report errors in a more script-friendly manner.
- 0 usually means everything went OK; anything different from 0 means an error occurred.
return code, 你可以指定返回任何值
return code as bool value:
Return codes can be used to conditionally execute commands using
&&(and operator) and||(or operator) .Commands can also be separated within the same line using a semicolon
;.The
trueprogram will always have a 0 return code and thefalsecommand will always have a 1 return code.false || echo "Oops, fail"
Oops, fail
true || echo "Will not be printed"
true && echo "Things went well"
Things went well
false && echo "Will not be printed"
true ; echo "This will always run"
This will always run
false ; echo "This will always run"
This will always run
Shebang
Note that scripts need not necessarily be written in bash to be called from the terminal. For instance, here’s a simple Python script that outputs its arguments in reversed order:
!/usr/local/bin/python |
shebang: the character sequence consisting of
#!at the beginning of a script in a Unix-like operating systemshell会将shebang中
#!之后的内容作为一个程序的路径,打开该程序, 将本script的路径当作参数传入( 即: 将整个script当作input传入shebang所指定的程序 )For example, if a script is named with the path path/to/script, and it starts with the following line,
#!/bin/sh, then the program loader is instructed to run the program /bin/sh, passing path/to/script as the first argument. In Linux, this behavior is the result of both kernel and user-space code.[9]The shebang line is usually ignored by the interpreter, because the "#" character is a comment marker in many scripting languages; some language interpreters that do not use the hash mark to begin comments still may ignore the shebang line in recognition of its purpose.
she-bang with env
The she-bang expects a full path to the interpreter to use so the following syntax would be incorrect:
#!python |
Setting a full path like this might work:
#!/usr/local/bin/python |
but would be non portable as python might be installed in /bin, /opt/python/bin, or wherever other location.
Using env
#!/usr/bin/env python |
is a method allowing a portable way to specify to the OS a full path equivalent to the one where python is first located in the PATH.
I/O redirection
一般情况下,每个 Linux 命令运行时都会打开三个文件:
标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息
Discard the output
command > /dev/null
The file /dev/null is a special file that automatically discards all its input.
Discard both output of a command and its error output,
command > /dev/null 2>&1
- a command normally writes its output to STDOUT
- use standard redirection to redirect STDERR to STDOUT
- 这里
2>&1将标准错误(2)合并到标准输出(1), 而标准输出已经被重定向到了/dev/null, 因此总体效果是,标准错误和输出都被重定向到了/dev/null
file descriptor:
- 0 : STDIN
- 1: STDOUT
- 2: STDERR
| Sr.No. | Command & Description |
|---|---|
| 1 | pgm > file Output of pgm is redirected to file 会覆盖目标文件中原有的数据 |
| 2 | pgm < file Program pgm reads its input from file |
| 3 | pgm >> file Output of pgm is appended to file |
| 4 | n > file Output from stream with descriptor n redirected to file |
| 5 | n >> file Output from stream with descriptor n appended to file |
| 6 | n >& m Merges output from stream n with stream m |
| 7 | n <& m Merges input from stream n with stream m |
| 8 | << tag Standard input comes from here through next tag at the start of line |
| 9 | | Takes output from one program, or process, and sends it to another |
Here Document
Here Document 目前没有统一的翻译,这里暂译为”嵌入文档“。Here Document 是 Shell 中的一种特殊的重定向方式,它的基本的形式如下:
command << delimiter |
它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command
注意:
- 结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。
- 开始的delimiter前后的空格会被忽略掉。
下面的例子,通过 wc -l 命令计算 document 的行数:
wc -l << EOF |
也可将 Here Document 用在脚本中,例如:
!/bin/bash |
https://lyk-love.cn/2023/04/24/Shell-Script-Searching-Path/↩︎