Shell脚本编程 脚本的需求 Hello脚本 加载脚本 命令行参数 变量和比较 交互式接受用户输入 if then else for循环 while循环 读取文件 调试 实际应用场景 资源列表 脚本的需求 自动化重复的手动任务 创建专门的自定义命令 脚本语言与编程语言的区别 注意: 通常用作壳脚本的扩展名 此处提供的材料是针对 的 Hello脚本 第一行有两个部分 is path of to get path called as shebang), directs the program loader to use the interpreter path provided Comments Comments start with Comments can be placed at
注意:
.sh 通常用作壳脚本的扩展名GNU bash, version 4.3.11(1)-release 的#!/bin/bash # Print greeting message echo "Hello $USER" # Print day of week echo "Today is $(date -u +%A)" # use single quotes for literal strings echo 'Have a nice day'
第一行有两个部分
/bin/bash is path of bash
类型 bash to get path#! called as shebang, directs the program loader to use the interpreter path providedComments
#echo 'Hello' # 代码注释结尾Single quotes vs Double quotes
echo 内置命令
help -d echo 将参数写入标准输出echo 会添加一个换行符且不解释反斜杠-n 不添加换行符-e 启用对后续反斜杠转义字符的解释-E 显式抑制对反斜杠转义字符的解释$ chmod +x hello_script.sh $ ./hello_world.sh Hello learnbyexample Today is Wednesday Have a nice day
$ help -d source source - Execute commands from a file in the current shell.
. or source command
~/.bashrc one can use source ~/.bashrc使更改立即生效$ # contents of prev_cmd.sh prev=$(fc -ln -2 | sed 's/^[ \t]*//;q') echo "$prev"
$ printf 'hi there\n' hi there $ bash prev_cmd.sh $ printf 'hi there\n' hi there $ source prev_cmd.sh printf 'hi there\n'
#!/bin/bash # Print line count of files given as command line argument echo "No of lines in '$1' is $(wc -l < "$1")" echo "No of lines in '$2' is $(wc -l < "$2")"
$1 $2 $3 etc$0 contains the name of the script itself - useful to code different behavior based on name of script used$@ array of all the command line arguments passed to script$# 开头的位置变量中。$# 表示传递给脚本的命令行参数的数量。$ ./command_line_arguments.sh hello_script.sh test\ file.txt No of lines in 'hello_script.sh' is 9 No of lines in 'test file.txt' is 5
dir_path=/home/guest space has special meaning in bash, cannot be used around = in variablesgreeting='hello world' use single quotes for literal stringsuser_greeting="hello $USER" use double quotes for substitutionsecho $user_greeting use $ when variable's value is neededno_of_lines=$(wc -l < "$filename") use double quotes around variables when passing its value to another commandnum=534 numbers can also be declared(( num = 534 )) but using (( )) for numbers makes life much easier(( num1 > num2 )) number comparisons are also more readable within (( ))[[ -e story.txt ]] test if the file/directory exists[[ $str1 == $str2 ]] 用于字符串比较进一步阅读
#!/bin/bash # Get user input echo 'Hi there! This script returns the sum of two numbers' read -p 'Enter two numbers separated by spaces: ' number1 number2 echo -e "\n$number1 + $number2 = $((number1 + number2))" echo 'Thank you for using the script, Have a nice day :)'
help -d read Read a line from the standard input and split it into fields-a array assign the words read to sequential indices of the array variable ARRAY, starting at zero-p prompt output the string PROMPT without a trailing newline before attempting to read-s 不回显来自终端的输入$ ./user_input.sh Hi there! This script returns the sum of two numbers Enter two numbers separated by spaces: 7 42 7 + 42 = 49 Thank you for using the script, Have a nice day :)
#!/bin/bash if (( $# != 2 )) then echo "Error!! Please provide two file names" # simple convention for exit values is '0' for success and '1' for error exit 1 else # Use ; to combine multiple commands in same line # -f option checks if file exists, ! negates the value # white-space around [[ and ]] is necessary if [[ ! -f $1 ]] ; then echo "Error!! '$1' is not a valid filename" ; exit 1 else echo "No of lines in '$1' is $(wc -l < "$1")" fi # Conditional Execution [[ ! -f $2 ]] && echo "Error!! '$2' is not a valid filename" && exit 1 echo "No of lines in '$2' is $(wc -l < "$2")" fi
if [[ ! -f $1 ]] ; then block is only intended for demonstration, we could as well have used error handling of wc command if file doesn't existexit value is 0 , so need not be explicitly written for successful script completionelif if you need to test more conditions after if&& is used to execute a command only when the preceding one successfully finishesecho "错误!! 请提供两个文件名" 1>&2 等等$ ./if_then_else.sh Error!! Please provide two file names $ echo $? 1 $ ./if_then_else.sh hello_script.sh Error!! Please provide two file names $ echo $? 1 $ ./if_then_else.sh hello_script.sh xyz.tzt No of lines in 'hello_script.sh' is 9 Error!! 'xyz.tzt' is not a valid filename $ echo $? 1 $ ./if_then_else.sh hello_script.sh 'test file.txt' No of lines in 'hello_script.sh' is 9 No of lines in 'test file.txt' is 5 $ echo $? 0
结合if和命令执行状态
有时需要知道预期命令操作是否成功,并根据结果采取行动。退出状态为 0 is considered as successful condition when used with if 语句。如果可用,使用适当的选项来抑制命令的stdout/stderr,否则可能需要重定向以避免在终端上造成混乱。
$ grep 'echo' hello_script.sh echo "Hello $USER" echo "Today is $(date -u +%A)" echo 'Have a nice day' $ # do not write anything to standard output $ grep -q 'echo' hello_script.sh $ echo $? 0 $ grep -q 'echo' xyz.txt grep: xyz.txt: No such file or directory $ echo $? 2 $ # Suppress error messages about nonexistent or unreadable files $ grep -qs 'echo' xyz.txt $ echo $? 2
例子
#!/bin/bash if grep -q 'echo' hello_script.sh ; then # do something echo "string found" else # do something else echo "string not found" fi
#!/bin/bash # Ensure atleast one argument is provided (( $# == 0 )) && echo "Error!! Please provide atleast one file name" && exit 1 file_count=0 total_lines=0 # every iteration, variable file gets next positional argument for file in "$@" do # Let wc show its error message if file doesn't exist # terminate the script if wc command exit status is not 0 no_of_lines=$(wc -l < "$file") || exit 1 echo "No of lines in '$file' is $no_of_lines" ((file_count++)) ((total_lines = total_lines + no_of_lines)) done echo -e "\nTotal Number of files = $file_count" echo "Total Number of lines = $total_lines"
for loop is useful if we need only element of an array, without having to iterate over length of an array and using an index for each iteration to get array elements|| to stop the script if wc fails i.e 'exit status' other than 0$ ./for_loop.sh Error!! Please provide atleast one file name $ echo $? 1 $ ./for_loop.sh hello_script.sh if_then_else.sh command_line_arguments.sh No of lines in 'hello_script.sh' is 9 No of lines in 'if_then_else.sh' is 21 No of lines in 'command_line_arguments.sh' is 5 Total Number of files = 3 Total Number of lines = 35 $ echo $? 0 $ ./for_loop.sh hello_script.sh xyz.tzt No of lines in 'hello_script.sh' is 9 ./for_loop.sh: line 14: xyz.tzt: No such file or directory $ echo $? 1
基于索引的for循环
#!/bin/bash # Print 0 to 4 for ((i = 0; i < 5; i++)) do echo $i done
迭代用户定义的数组
$ files=('report.log' 'pass_list.txt') $ for f in "${files[@]}"; do echo "$f"; done report.log pass_list.txt
由通配符模式指定的文件
常见的错误是使用 ls 命令的输出,这容易出错且没有必要。相反,可以直接使用这些参数。
$ ls pass_list.txt power.log report.txt $ for f in power.log *.txt; do echo "$f"; done power.log pass_list.txt report.txt
#!/bin/bash # Print 5 to 1 (( i = 5 )) while (( i != 0 )) do echo $i ((i--)) done
while 循环$ ./while_loop.sh 5 4 3 2 1
逐行读取
#!/bin/bash while IFS= read -r line; do # do something with each line echo "$line" done < 'files.txt'
IFS is used to specify field separator which is by default whitespace. IFS= will clear the default value and prevent stripping of leading and trailing whitespace of lines-r option for read will prevent interpreting \ 转义$ cat files.txt hello_script.sh if_then_else.sh $ ./while_read_file.sh hello_script.sh if_then_else.sh
按不同字段读取行
IFS 指定不同的分隔符$ cat read_file_field.sh #!/bin/bash while IFS=: read -r genre name; do echo -e "$genre\t:: $name" done < 'books.txt' $ cat books.txt fantasy:Harry Potter sci-fi:The Martian mystery:Sherlock Holmes $ ./read_file_field.sh fantasy :: Harry Potter sci-fi :: The Martian mystery :: Sherlock Holmes
一次读取'n'个字符
$ while read -n1 char; do echo "Character read is: $char"; done <<< "\word" Character read is: w Character read is: o Character read is: r Character read is: d Character read is: $ # if ending newline character is not desirable $ while read -n1 char; do echo "Character read is: $char"; done < <(echo -n "hi") Character read is: h Character read is: i $ while read -r -n2 chars; do echo "Characters read: $chars"; done <<< "\word" Characters read: \w Characters read: or Characters read: d
-x Print commands and their arguments as they are executed-v verbose option, print shell input lines as they are readset -xv 使用此命令从脚本内部启用调试$ bash -x hello_script.sh + echo 'Hello learnbyexample' Hello learnbyexample ++ date -u +%A + echo 'Today is Friday' Today is Friday + echo 'Have a nice day' Have a nice day
$ bash -xv hello_script.sh #!/bin/bash # Print greeting message echo "Hello $USER" + echo 'Hello learnbyexample' Hello learnbyexample # Print day of week echo "Today is $(date -u +%A)" date -u +%A ++ date -u +%A + echo 'Today is Friday' Today is Friday # use single quotes for literal strings echo 'Have a nice day' + echo 'Have a nice day' Have a nice day
创建这些章节时,涉及大量复制粘贴命令及其输出,因此难免会出现错误。因此,编写一个脚本来检查正确性非常有用。考虑以下markdown文件
## <a name="some-heading"></a>Some heading 一些解释 ```bash $ seq 3 1 2 3 $ printf 'hi there!\n' hi there! ``` ## <a name="another-heading"></a>Another heading 更多解释 ```bash $ help -d readarray readarray - Read lines from a file into an array variable. $ a=5 $ printf "$a\n" 5 ```
eval用于已知命令,就像这里的情况一样。#!/bin/bash cb_start=0 readarray -t lines < 'sample.md' for ((i = 0; i < ${#lines[@]}; i++)); do # mark start/end of command block # Line starting with $ to be verified only between ```bash 和 ``` block end [[ ${lines[$i]:0:7} == '```bash' ]] && ((cb_start=1)) && continue [[ ${lines[$i]:0:3} == '```' ]] && ((cb_start=0)) && continue if [[ $cb_start == 1 && ${lines[$i]:0:2} == '$ ' ]]; then cmd="${lines[$i]:2}" # collect command output lines until line starting with $ or ``` block end cmp_str='' j=1 while [[ ${lines[$i+$j]:0:2} != '$ ' && ${lines[$i+$j]:0:3} != '```' ]]; do cmp_str+="${lines[$i+$j]}" ((j++)) done ((i+=j-1)) cmd_op=$(eval "$cmd") if [[ "${cmd_op//$'\n'}" == "${cmp_str//$'\n'}" ]]; then echo "Pass: $cmd" else echo "Fail: $cmd" fi fi done
$ ./verify_cmds.sh Pass: seq 3 Pass: printf 'hi there!\n' Pass: help -d readarray Pass: a=5 Fail: printf "$a\n" $ source verify_cmds.sh Pass: seq 3 Pass: printf 'hi there!\n' Pass: help -d readarray Pass: a=5 Pass: printf "$a\n"
本章的内容只是一个简单的介绍。
Shell 脚本
特定主题
实用工具、技巧和参考
致谢:github.com/learnbyexample/Linux_command_line
声明:
本文件灏天文库团队进行了翻译。尽管我们力求准确,但请注意,翻译可能包含错误或不准确之处。原文档以其原始语言为准。我们不对因使用此翻译而产生的任何误解或误译负责。