基本变量
$0- Name of the script$1to$9- Arguments to the script.$1is the first argument and so on.$@- All the arguments$#- Number of arguments$?- Return code of the previous command$$- 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+.
常见用法
用法
- Commands can also be separated within the same line using a semicolon
;.
false ; echo "This will always run"
# This will always run (两个命令都会被执行)
- get the output of a command as a variable. This can be done with command substitution. Whenever you place
$( CMD )it will executeCMD, get the output of the command and substitute it in place. (假设我们有一个叫做CMD的命令,所以这个语法是$(var)) - A lesser known similar feature is process substitution,
<( CMD )will executeCMDand place the output in a temporary file and substitute the<()with that file’s name. This is useful when commands expect values to be passed by file instead of by STDIN. (所以这个语法是<($var), 将输出写入到一个临时文件,然后<()代表这个临时文件的名字)
For example,
diff <(ls foo) <(ls bar)will show differences between files in dirsfooandbar.
一个简单脚本
#!/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
When performing comparisons in bash, try to use double brackets [[ ]] in favor of simple brackets [ ]
用法
继续看用法
- Wildcards
- Whenever you want to perform some sort of wildcard matching, you can use
?and*to match one or any amount of characters respectively. For instance, given filesfoo,foo1,foo2,foo10andbar, the commandrm foo?will deletefoo1andfoo2whereasrm foo*will delete all butbar.
- Whenever you want to perform some sort of wildcard matching, you can use
- Curly braces
{}- Whenever you have a common substring in a series of commands, you can use curly braces for bash to expand this automatically. This comes in very handy when moving or converting files.
# 假设我们现在有一个image.png文件
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
- shellcheck
- Writing
bashscripts can be tricky and unintuitive. There are tools like shellcheck that will help you find errors in your sh/bash scripts. (shellcheck是一个命令去检查我们的sh脚本,它不是预定义的命令,需要用户自己安装:brew install shellcheck)
- Writing
函数和脚本的区别
- Functions have to be in the same language as the shell, while scripts can be written in any language. This is why including a shebang for scripts is important.
- Functions are loaded once when their definition is read. Scripts are loaded every time they are executed. This makes functions slightly faster to load, but whenever you change them you will have to reload their definition.
- Functions are executed in the current shell environment whereas scripts execute in their own process. Thus, functions can modify environment variables, e.g. change your current directory, whereas scripts can’t. Scripts will be passed by value environment variables that have been exported using
export - As with any programming language, functions are a powerful construct to achieve modularity, code reuse, and clarity of shell code. Often shell scripts will include their own function definitions.
上面的解释有点难理解,让我们通过一些具体的例子来更清晰地理解 函数 和 脚本 在访问和修改环境变量时的区别,以及它们之间的关系。
例子 1:函数直接修改环境变量
假设我们有一个简单的脚本 modify_var.sh,其中定义了一个函数:
#!/bin/bash
# 定义一个函数,修改环境变量
change_directory() {
# 修改当前目录
cd /tmp
# 设置一个新的环境变量
MY_VAR="Hello, World!"
}
# 调用函数
change_directory
# 打印当前目录和环境变量
echo "Current directory: $(pwd)"
echo "MY_VAR: $MY_VAR"
运行脚本的结果: ./modify_var.sh
Current directory: /tmp
MY_VAR: Hello, World!
例子 2:脚本无法直接修改当前 shell 的环境变量
接下来,我们看看另一个脚本 set_var.sh,这个脚本尝试设置环境变量,但它在一个独立的进程中运行:
#!/bin/bash
# 定义一个函数,尝试修改环境变量
set_variable() {
MY_VAR="Goodbye, World!"
}
# 调用函数
set_variable
# 打印环境变量
echo "Inside script, MY_VAR: $MY_VAR"
运行脚本的结果: ./set_var.sh
Inside script, MY_VAR:
函数 set_variable 尝试设置环境变量 MY_VAR,但当脚本运行完成后,该变量并没有在当前 shell 中存在。 这是因为脚本是在一个独立的进程中运行的。它对环境变量的修改不会影响到调用它的 shell 环境。
例子 3:使用 export 传递环境变量 如果我们希望在脚本中设置一个环境变量并让其在调用脚本后仍然有效,我们可以使用 export 命令。下面是一个新的脚本 export_var.sh:
#!/bin/bash
# 定义一个函数,使用 export 设置环境变量
set_variable() {
export MY_VAR="Hello from export!"
}
# 调用函数
set_variable
# 打印环境变量
echo "Inside script, MY_VAR: $MY_VAR"
运行脚本的结果: ./export_var.sh
Inside script, MY_VAR: Hello from export!
$ echo $MY_VAR # 在当前 shell 中检查
Hello from export!
在这个例子中:
- 函数 set_variable 使用 export 命令将环境变量 MY_VAR 导出。
- 因此,尽管脚本在一个独立的进程中运行,但因为使用了 export,环境变量的值被传递到了当前 shell。
Shell Tools
man
- man命令可以帮助我们查看命令使用指南,但是它的信息一般来说太过繁杂,难以理解。[TLDR pages](https://tldr.sh/) are a nifty complementary solution that focuses on giving example use cases of a command so you can quickly figure out which options to use。 (需要手动安装brew install tldr)
find
- find命令帮我们查询文件或目录(是查找文件名称或类型,不包含文件内容)
# Find all directories named src, -iname是大小写不敏感, -name是大小写敏感
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有时太过繁琐,难以记住用法,总会有替代的工具。For instance, fd is a simple, fast, and user-friendly alternative to find. 安装:brew install fd
grep
grep跟find不同的是,它搜索的是文件内容.有一个比grep更好用的工具是rg 。安装:brew install 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
history
history可以查看用过的shell commoand,有一个更好用的方法就是按 ctrl + R,这个会搜索符合你的pattern的历史命令,按左右方向键,就是退出搜索模式,然后你可以修改选中的shell命令。一直按ctrl+R,终端会在历史记录中向上继续搜索下一个匹配项,直到找到你需要的命令