基础的Bash用法
Shell脚本
本章介绍了基本的Bash用法
变量定义
Bash支持自定义变量,语法为foo=bar
,访问变量的语法为$foo
这里需要注意foo = bar
是不行的,会被解析成执行foo
并传入= bar
两个参数
字符串
Bash中的字符串可以用'
或"
分隔符定义,但是这两者是有区别的。用"
定义的字符串会进行变量值替换
foo=bar
echo "$foo"
# prints bar
echo '$foo'
# prints $foo
运行时变量
Bash同样支持创建函数与传递参数
mcd () {
mkdir -p "$1"
cd "$1"
}
这里的$1
表示函数的第一个参数。Bash中有很多这类变量,如下是一些常用的特殊变量:
$0
脚本的名字$1
to$9
参数$@
全部的参数$#
参数的数量$?
上个命令的返回值$$
当前脚本的进程标志数字!!
上一次运行的完整命令行,常用的命令例如sudo !!
$_
上一次运行的命令行的最后一个参数
返回值 与 条件运算符
一般来说,命令行的使用STDOUT
打印输出,STDERR
打印错误,一个返回值来报告脚本运行状态
返回值0表示一切正常,非0值表示有错误发生。这些值可以被&&
与||
连接进行条件运算。另外,处在同一行中的命令可以使用;
分割为独立的部分。true
表示0,false
将表示1
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"
#
false ; echo "This will always run"
# This will always run
替换模式
一个常见的情景是将一个命令行或脚本的输出作为变量。命令行替换(command substitution)可以完成这个工作。当命令行中出现$(CMD)
时,Bash将执行CMD
,得到输出并原地替换,例如for file in $(ls)
另一个不太为人熟知的是过程替换(process subsstitution),<(CMD)
将会执行CMD
并将输出保存至一个临时文件中,并将自己替换为文件名。对于通过文件而不通过STDIN
传递参数的命令极为实用,例如diff <(ls foo) <(ls bar)
比较
下面的例子将会检查所有输入的文件,并给没有foobar的文件末尾加入foobar
#!/bin/bash
echo "Starting program at $(date)" # Date will be substituted
echo "Running program $0 with $# arguments with pid $$"
for file in $@; do
grep foobar $file > /dev/null 2> /dev/null
# When pattern is not found, grep has exit status 1
# We redirect STDOUT and STDERR to a null register since we do not care about them
if [[ $? -ne 0 ]]; then
echo "File $file does not have any foobar, adding one"
echo "# foobar" >> "$file"
fi
done
例子中将$?
与0进行比较。事实上Bash支持很多这类操作,详见man test
。
当进行比较时,尽可能使用[[ ]]
而不是[ ]
。这样出错的机率更低,尽管这并不能直接移植到sh
上。具体原因见这里
命令行展开
在执行命令行时,常常遇到需要输入相似的参数的时刻。Bash提供了一种方便的方式去拓展你的输入。
- 野卡(wildcards)当你需要模糊匹配的时候使用
?
与*
类匹配一个或多个不确定的字符 - 大括号(Curly braces)
{}
当你的参数拥有共同的字串时可以自动展开为每个参数
convert image.{png,jpg}
# Will expand to
convert image.png image.jpg
cp /path/to/project/{foo,bar,baz}.sh /newpath
# Will expand to
cp /path/to/project/foo.sh /path/to/project/bar.sh /path/to/project/baz.sh /newpath
# Globbing techniques can also be combined
mv *{.py,.sh} folder
# Will move all *.py and *.sh files
mkdir foo bar
# This creates files foo/a, foo/b, ... foo/h, bar/a, bar/b, ... bar/h
touch {foo,bar}/{a..h}
touch foo/x bar/y
# Show differences between files in foo and bar
diff <(ls foo) <(ls bar)
# Outputs
# < x
# ---
# > y
写shell是不太直观的,这里有个工具可以帮你找出错误
Shebang line
不是所有的从命令行里运行的脚本都得是Bash脚本。
#!/usr/local/bin/python
import sys
for arg in reversed(sys.argv[1:]):
print(arg)
该例子的第一行便是shebang line,指出了该脚本需要的解释器
函数与脚本的区别
- 函数在shell中必须是相同的语言,而脚本可以用任何语言完成。
- 函数只会被加载一次,当他们的定义被读入时。脚本则会在每次执行时被加载。所以当需要复用时,函数会稍快一点。
- 函数运行在当前shell的环境中,而脚本运行于独立的进程中。也就是说函数可以改变当前shell的环境变量,而脚本不能。脚本需要使用
export
传递环境变量。
Shell工具
找到命令的用法
-h or --help
man
- TLDR
找到文件
find
是个类UNIX系统都有的非常好的工具
# Find all directories named src
find . -name src -type d
# Find all python files that have a folder named test in their path
find . -path '**/test/**/*.py' -type f
# Find all files modified in the last day
find . -mtime -1
# Find all zip files with size in range 500k to 10M
find . -size +500k -size -10M -name '*.tar.gz'
还可以执行命令
# Delete all files with .tmp extension
find . -name '*.tmp' -exec rm {} \;
# Find all PNG files and convert them to JPG
find . -name '*.png' -exec convert {} {.}.jpg \;
虽然find
被大量使用,但是参数还是太复杂了,例如find -name '*PATTERN*'
是严格match,-iname
是大小写不敏感系列。输出也不好看。这里是一个好用的替代。此外还有一些基于数据库的locate
可以使用,具体参见手册。
找到代码
grep
用于在输入的文本中找到对应的模式。-C
可以用于输出匹配行的上下文信息,-v
用于反向(inverting)匹配(打印出没有匹配上的行)。
但是grep对搜索大量文件还是功能不足,比如跳不过.git
文件夹。作者推荐用ripgrep作为替代。
# Find all python files where I used the requests library
rg -t py 'import requests'
# Find all files (including hidden files) without a shebang line
rg -u --files-without-match "^#!"
# Find all matches of foo and print the following 5 lines
rg foo -A 5
# Print statistics of matches (# of matched lines and files )
rg --stats PATTERN
找到Shell指令
一般来说在shell历史中查找用history | grep find
就可以了。在大部分shell中Ctrl+R
可以在历史中反向搜索。按下组合键之后输入要搜索的命令的子串之后,反复使用该组合键可以循环浏览能够匹配上子串的命令。该组合键与fzf搭配使用效果更佳。
还有一个相关功能是基于历史shell的自动补全。zsh内已经带了这个功能。
还有一点,若以空格开始一条命令,则这条命令不会被加入history。对于要输入敏感信息的命令非常有用
目录导航
用静态链接ln -s
建立文件夹之间的快速跳转已经是老生常谈了,但是这对于快速访问相关文件是不够的。fasd是一个提供最近访问文件的工具,根据最近与频率进行排序的算法