Bash 15分钟

Bash 15 分钟

背景

偶尔翻了之前的bash 编程的书,一些语法和细节发现可能还需要再review 下,然后就翻在线的ABS(Advanced Bash Scripting) ,发现这篇文档还是太长了,而且英文的东西,当时看了明白了,后面忘记了再去查,就有点需要花点时间找找在哪里看过。而且记得10年前好像看过一个word版本的bash 指南,写的相当简练,现在也忘记名字了,所以想想还是弄个简表,第一个是自己总结,第二个方便以后查找。我本来想直接用这个Learn X in Y ,因为他的格式比较符合我想记录的方式,但是内容上感觉有些地方没有说全,而且有些地方有错误的写法。我想我就以这篇为基础,然后扩充修改下这个bash教程吧,我同时也参考了其他的Bash 15分钟教程之类的内容,以免遗漏。

内容


#!/bin/bash
# 脚本的第一行叫 shebang,用来告知系统如何执行该脚本:
# 参见: http://en.wikipedia.org/wiki/Shebang_(Unix)
# 如你所见,注释以 # 开头,shebang 也是注释。

# 单行注释
echo "A comment will follow"  # 语句后的注释
   # 注释前面有空格也OK
echo "# this is string start with shebang" # 在引号中不起作用
ehco '# this is anothing string start with shebang' # '#'在引号中不起作用
echo the \# string      # 不在引号中的'#'要转义

# 每一句指令以换行或分号隔开:
echo 'This is the first line'; echo 'This is the second line'

# 声明一个变量:
Variable="Some string"

# 下面是错误的做法:
Variable = "Some string"
# Bash 会把 Variable 当做一个指令,由于找不到该指令,因此这里会报错。

# 也不可以这样:
Variable= 'Some string'
# Bash 会认为 'Some string' 是一条指令,由于找不到该指令,这里再次报错。
# (这个例子中 'Variable=' 这部分会被当作仅对 'Some string' 起作用的赋值。)

# 使用变量:
echo $Variable
echo "$Variable"
echo '$Variable'
# 当你赋值 (assign) 、导出 (export),或者以其他方式使用变量时,变量名前不加 $。
# 如果要使用变量的值, 则要加 $。
# 注意: ' (单引号) 不会展开变量(即会屏蔽掉变量)。

# 变量加双引号和不加双引号的区别
para="a b c d"
ls $para                    # 使用a,b,c,d 四个参数执行ls 相当于ls a b c d
ls "$para"                  # 使用一个参数执行ls ,相当于 ls "a b c d"

## 双括号结构中可以使用类似C语言的 cond?result-if-true:result-if-false
(( var = 1>2?3:4))

: # 冒号为空指令文件,返回为0

# 单花括号中的?可以用来检测变量是否被设置
# ${var?error_info},可以用来做必要参数检查

: ${var?}
: ${var?test message}

# 空指令版本的注释
: <<MULTILINECOMMENT
coomandd
\sds
~2@*
MULTILINECOMMENT

# 扩展单引号中被转义的字符串为ACSII
echo $'\n\n\n'              

# 变量赋值,使用命令执行结果,等同于echo `cat /etc/hostname`
echo $(cat /etc/hostname)

# 变量扩展或者置换

${param}                    # 等同$param,特定情况下这种${param}的更严格的书写模式才工作         
${param:-default}           # 建议这种,当param 被decare 但是没有赋值的时候,该表达式工作良好
${param:=default}           # If parameter not set, set it to default
${param:+default}           # If parameter set ,use alt, else use null
${param?error_mesage}       # If param not set ,error message
${#var}                     # var string length,if var is array ,return the first
${var#pattern}              # 从var中前面移除最短的pattern匹配
${var##pattern}             # 从var中前面移除最长的pattern匹配
${var%pattern}              # 从var中后面移除最短的pattern匹配
${var%%pattern}             # 从var中前面移除最长的pattern匹配

# 变量截取
${var:pos}                  # 从var的pos 位置开始截取,直到最后
${var:pos:len}              # 从var的pos 位置开始截len长度
${var/pattern/replace}      # 从var中以replace替换第一个Pattern匹配
${var//pattern//replace}    # 从var中以replace替换所有Pattern匹配
${var/#pattern/replace}     # 如果var的开头匹配pattern,拿replace 替换第一个匹配
${var/%pattern/replace}     # 如果var的结尾匹配pattern,拿replace 替换第一个匹配

${!varprefix*}              # 获取所有以prefix开头的变量 同${!varprefix@}

# 内置变量:
# 下面的内置变量很有用
echo "Last program return value: $?"
echo "Script's PID: $$"
echo "Number of arguments: $#"
echo "Scripts arguments: $@"
echo "Scripts arguments separated in different variables: $1 $2..."

: << SPECIALVARS
"$*"                        # 所有命名参数  
"$@"                        # TODO
"$?"                        # 命令、脚本的退出状态
"$!"                        # 最后一个后台执行的任务的PID
"$_"                        # 上一个命令的最后一个内容参数,如果命令没给任何参数,返回命令本身
"$$"                        # 脚本PID
$#                          # 脚本传入参数数量
$@                          # 脚本传入的所有参数
${!#}                       # 脚本最后一个传入参数
$1_                         # 位置参数变量后跟下划线,可以防止参数没有输入的情况
SPECIALVARS

# 读取输入:
echo "What's your name?"
read Name # 这里不需要声明新变量
echo Hello, $Name!

# 根据上一个指令执行结果决定是否执行下一个指令
echo "Always executed" || echo "Only executed if first command fails"
echo "Always executed" && echo "Only executed if first command does NOT fail"

# 数值比较单方括号内
a=2
[ 1 -eq $a ] && echo true || echo false  # false
[ 1 -ne $a ] && echo true || echo false  # true 
[ 1 -gt $a ] && echo true || echo false  # false
[ 1 -ge $a ] && echo true || echo false  # false
[ 1 -lt $a ] && echo true || echo false  # true
[ 1 -le $a ] && echo true || echo false  # true

# 数值比较双括号内
(( 1 < "$a" )) && echo true || echo false # true
(( 1 > "$a" )) && echo true || echo false # false
(( 1 <= "$a" )) && echo true || echo false # true
(( 1 >= "$a" )) && echo true || echo false # false

# 字符串比较
s="A"
[ "s" == "$s" ] && echo true || echo false # false
[ "s" != "$s" ] && echo true || echo false # true
[ "a" > "$s" ] && echo true || echo false # true 比较ascii 小a 大于大A
[ "X" < "$s" ] && echo true || echo false # true
[ -n "$s" ] && echo "string is not null" || echo "string is null" # string is not null
[ -z "$s" ] && echo "string is null" ||echo "string is not null"  # string is not null

# 文件test

[ -x /bin/bash ] && echo "bash is executable"
# 其他的test 可以用help test 列举。

# 通常的 if 结构看起来像这样:
# 'man test' 可查看更多的信息,由于$USER 为字符串,所以我们需要用!= 这些字符串比较符号
if [ $Name != $USER ]
then
    echo "Your name isn't your username"
else
    echo "Your name is your username"
fi

# 在 if 语句中使用 && 和 || 需要多对方括号
if [ $Name == "Steve" ] && [ $Age -eq 15 ]
# 也可以写成(注意双方括号中的&&) if [[ $Name == "Steve" && $Age -eq 15 ]]
# 也可以写成(注意单方括号中的-a) if [ $Name == "Steve" -a $Age  -eq 15 ]
then
    echo "This will run if $Name is Steve AND $Age is 15."
fi

if [ $Name == "Daniya" ] || [ $Name == "Zach" ]
# 也可以写成(注意双方括号中的||) if [[ $Name == "Daniya" || $Name == "Zach" ]]
# 也可以写成(注意单方括号中的-o) if [ $Name == "Daniya" -o $Name == "Zach" ]
then
    echo "This will run if $Name is Daniya OR Zach."
fi

# 花括号扩展
touch a{1,2,3}              # 会在当前目录创建a1,a2,a3
touch {a,b,c}{1,2,3}        # 会创建a1,a2,a3,b1,b2,b3,c1,c2,c3
echo {a..z}                 # 从a扩展到z .a,b,c,d,e....x,y,z
echo {1..9}                 # 从1至9

# Inline group,或者可以成为匿名函数
{ a=123; } ; echo $a        # 花括号中的变量可以在脚本后续代码中使用,不像function中的变量,出了function的作用域,function里面的变量无法在外部使用

# 数学计算,有三种方式
z=1
z=`expr $z + 3` # backtricks
echo $z
z=$((z+3))      # 双括号
echo $z
let "z += 3"    # let
echo $z

# 与其他编程语言不同的是,bash 运行时依赖上下文。比如,使用 ls 时,列出当前目录。
ls

# 指令可以带有选项:
ls -l # 列出文件和目录的详细信息

# 前一个指令的输出可以当作后一个指令的输入。grep 用来匹配字符串。
# 用下面的指令列出当前目录下所有的 txt 文件:
ls -l | grep "\.txt"

# 重定向输入和输出(标准输入,标准输出,标准错误)。
# 以 ^EOF$ 作为结束标记从标准输入读取数据并覆盖 hello.py :
# EOF这种方式为HERE String 
cat > hello.py << EOF
#!/usr/bin/env python
from __future__ import print_function
import sys
print("#stdout", file=sys.stdout)
print("#stderr", file=sys.stderr)
for line in sys.stdin:
    print(line, file=sys.stdout)
EOF

# HereString 前面带-号,可以抑制文档内部的开头tab,注意不是space
cat <<-ENDOFMESSAGE
    This is line 1 of the message.
    This is line 2 of the message.
    This is line 3 of the message.
    This is line 4 of the message.
    This is the last line of the message.
ENDOFMESSAGE

# 重定向可以到输出,输入和错误输出。
python hello.py < "input.in"
python hello.py > "output.out"
python hello.py 2> "error.err"
python hello.py > "output-and-error.log" 2>&1
python hello.py > /dev/null 2>&1
# > 会覆盖已存在的文件, >> 会以累加的方式输出文件中。
python hello.py >> "output.out" 2>> "error.err"

# 覆盖 output.out , 追加 error.err 并统计行数
info bash 'Basic Shell Features' 'Redirections' > output.out 2>> error.err
wc -l output.out error.err

# 以 "#helloworld" 覆盖 output.out:
cat > output.out <(echo "#helloworld")
echo "#helloworld" > output.out
echo "#helloworld" | cat > output.out
echo "#helloworld" | tee output.out >/dev/null

# 清理临时文件并显示详情(增加 '-i' 选项启用交互模式)
rm -v output.out error.err output-and-error.log

# Bash 的 case 语句与 Java 和 C++ 中的 switch 语句类似: 注意结尾的双;;是为了转义;
case "$Variable" in
    # 列出需要匹配的字符串
    [[:upper:]]) echo "The letter is upper.";;
    [[:lower:]]) echo "The letter is lower.";;
    [0-9]) echo "It 's a number";;
    *) echo "may be special letter ";;
esac

# 循环遍历给定的参数序列:

for Variable in {1..3}
# 或 for Variable in "A" "B" "C"
# 或 for Variable in `seq 2 6`
# 或 for Variable in $(ls)
# 或 for variable in *.sh;  # *.sh 在bash中会扩展成本目录下所有.sh结尾的文件
do
    echo "$Variable"
done

# 或传统的 “for循环” :
for ((a=1; a <= 3; a++))
do
    echo $a
done

# while 循环:
while [ true ]
do
    echo "loop body here..."
    break # break 可以跳出整个循环 # continue ,可以跳过该次循环
done

# Util 循环

until false
do 
    echo "loop body here..."
done

# 你也可以使用函数
# 定义函数:
function foo ()
{
    echo "Arguments work just like script arguments: $@"
    echo "And: $1 $2..."
    echo "This is a function"
    return 0
}

# 更简单的方法
bar ()
{
    echo "Another way to declare functions!"
    return 0
}

# 调用函数
foo "My name is" $Name

# 正则表达式(在双方括号中使用)

t="abc123"
[[ "$t" == abc* ]]         # true (globbing比较)
[[ "$t" == "abc*" ]]       # false (字面比较)
[[ "$t" =~ [abc]+[123]+ ]] # true (正则表达式比较)
[[ "$t" =~ "abc*" ]]       # false (字面比较)

# 数组操作
base64_charset=( {A..Z} {a..z} {0..9} + / = )
echo $base64_charset                # 只会输出数组的第一个项目的值
echo ${base64_charset[*]}           # 输出A-Z a-z 0-9 + / =
echo ${base64_charset[0]}           # 输出A
echo ${#base64_charset[*]}          # 输出数组长度65
echo ${base64_charset[*]:1:2}       # 数组分片,输出B C
base64_charset[2]="O"               # 对指定项目赋值,或者修改值   
echo ${!base64_charset[*]}          # 对指定输出所有数组索引

# for 遍历数组
for i in ${base64_charset[*]}
do 
    echo $i
done

for i in ${!base64_charset[*]}
do 
    echo ${base64_charset[$i]}
done

# 有很多有用的指令需要学习:
# 打印 file.txt 的最后 10 行
tail -n 10 file.txt
# 打印 file.txt 的前 10 行
head -n 10 file.txt
# 将 file.txt 按行排序
sort file.txt
# 报告或忽略重复的行,用选项 -d 打印重复的行
uniq -d file.txt
# 打印每行中 ',' 之前内容
cut -d ',' -f 1 file.txt
# 将 file.txt 文件所有 'okay' 替换为 'great', (兼容正则表达式)
sed -i 's/okay/great/g' file.txt
# 将 file.txt 中匹配正则的行打印到标准输出
# 这里打印以 "foo" 开头, "bar" 结尾的行
grep "^foo.*bar$" file.txt
# 使用选项 "-c" 统计行数
grep -c "^foo.*bar$" file.txt
# 如果只是要按字面形式搜索字符串而不是按正则表达式,使用 fgrep (或 grep -F)
fgrep "^foo.*bar$" file.txt 

# 以 bash 内建的 'help' 指令阅读 Bash 自带文档:
help
help help
help for
help return
help source
help .

# 用 man 指令阅读相关的 Bash 手册
apropos bash
man 1 bash
man bash

# 用 info 指令查阅命令的 info 文档 (info 中按 ? 显示帮助信息)
apropos info | grep '^info.*('
man info
info info
info 5 info

# 阅读 Bash 的 info 文档:
info bash
info bash 'Bash Features'
info bash 6
info --apropos bash

# Bash 调试
set -u #  Treat unset variables as an error when substituting 对未设置值的变量报告错误
set -e #  如果命令的执行返回不为0则退出
set -n # 执行语法检查而不要运行脚本,等同于bash -n script.sh
set -v # 输出每个命令,在执行每个命令之前,等同于bash -v script.sh
set -x # 和-v 类似,但是输出时,会在每个命令前添加+,这样可以快速区分出命令和输出

# trap ,在接收到指定信号后,执行特定action 
# 信号可以trap -l 列出
trap 'echo script exit' EXIT                            # 在脚本退出时,打印上面内容
trap 'rm -f $TEMPFILE; exit $USER_INTERRUPT' TERM INT   # 在按CTRL+C时,执行清理任务

猜你喜欢

转载自blog.51cto.com/yoke88/2125119