Shell Script 写作方法与 Linux 命令备忘录

这是一个个人笔记的收录处,乐观地看,我会不断更新它来收录新内容。

Shell Script 中的控制语法

命令替换 Command Substitution

命令替换的方法为反引号 `command` 或者$(command),将 command 的执行结果(输出)放在原处。例如:

get_username() {
  echo "john" 
}
username=$(get_username)
DATE=$(date)

算术求值

形如 expr 11 / 2 或者 $((1 + 3))。将算术求值和命令替换结合在一起的例子:

calculate_sum() {
  echo $(( $1 + $2 )) # arithmetic evaluation, not command substitution.
}
result=$(calculate_sum 5 7) # command substitution
echo "The sum is: $result" # 12

注意,算术求值默认使用整型运算。所以 expr 11 / 2 会打印出 5。

接收参数

$2 或者 ${2} 用来代指第二个参数,这在上面的例子中已经体现了。对于 shell script 来说,$0 是 shell script 的文件名。$@ 是所有参数。

echo $0
for arg in "$@"; do
    echo "argument" ${arg}
    if [[ ${arg} == "--task_dir" ]]; then
        echo "\${2}" "${2}"
    fi
done

如果运行上述脚本 ./loop_all.sh C --task_dir A B,则预期的输出为

./loop_all.sh
argument C
argument --task_dir
${2} --task_dir
argument A
argument B

变量替换与通配符扩展

上面的例子中用到了变量替换(variable expansion):$name 或者 ${name} 都会将变量名称替换为变量内容,与命令替换的 $(command) 相对照。注意命令替换即使是在双引号内也会发生,不过在单引号内不会发生:

$ echo "I am $USER"
I am cloud-user
$ echo 'I am $USER'
I am $USER

如果在双引号内替换,且在变量后面并不刚好是空格,则就必须使用 ${name} 的形式了:

for subset in "science" "tech"; do
  if [ -f "/absolute/path/here/encode-${subset}.pkl"]; then
    echo "encode-${subset}.pkl exists"
  else
    echo "encode-${subset}.pkl does not exist"
  fi
done

通配符扩展(globbing)不会在双引号中发生,这在下面的例子中比较显然:

$ ls *.txt
a.txt b.txt
$ ls "*.txt"
ls: cannot access '*.txt': No such file or directory

这因此,可以通过双引号避免通配符扩展,这在下面的例子中就不那么显然了:

$ python example.py --resource_files encoding.*.pkl
example.py 会接收到通配符展开后的所有路径
$ python example.py --resource_files "encoding.*.pkl"
example.py 中 args.resource_files 为字符串 "encoding.*.pkl"

常见运算符

整型比大小:-eq -ne -gt -lt -ge -le。注:只支持作为条件,在 square bracket 中使用,如下例:

if [$a -ne $b] then
  echo "not equal"
fi

逻辑运算符:-o (or), -a (and), !, &&, ||。例:if [[ $a -lt 100 && $b -gt 100 ]]

[ -z $a ] 检测字符串长度是否为零,[ -n “$a” ] 检查字符串长度是否不为零。单括号和双括号的区别在这里

还有文件测试运算符:-b (is block device?), -c (is character device?), -d (is directory?), -f (is regular file nor dir or device?), -g, -k, -p, -u, -{r,w,x}, -s, -e (file exists?)

一个例子:使用 -eq 来检查上一个命令是否无错退出:

if [ $? -eq 0 ]; then
  echo "previous command successful"
else
  echo "previous command error, returned $?"
fi

输入输出与串接命令技巧

如果想要一并忽略某个程序的所有标准错误输出流(stderr),可以将其重定向,如 program -arg argument 2> /dev/null

在管道中串接 Python 命令也许是可行的:program -arg argument | python -c "import sys; result=sys.stdin.readlines()[-1]; print(result.split()[-1], end=',')" | tee -a out.csv

对于 echo 输出后跟随的换行,可以用 -n 命令行参数去掉。

常用 Linux 命令备忘录

cd 和 sudo

cd 是 shell 内置的命令,并不对应非可执行文件(which cd 返回空)。

sudo 工作的原理是 fork 一个进程出来,execve 执行要执行的可执行文件。/usr/bin/sudo 的权限位是 -rwsr-xr-x,拥有者是 root:root,这保证任何用户可以执行 sudo,而执行 sudo 产生的进程的 UID 为 root,拥有足够的权限。

由于 cd 不对应可执行文件,因此 sudo cd 会报错 sudo: cd: command not found。

find

find [-H] [-L] [-P] [-D debugopts] [-Olevel] [starting-point...] [expression]

概要:以 starting-point 为根出发遍历文件树,依照 expression 中列出的标准匹配文件或文件夹,并对成功匹配的文件或文件夹做出相应行为。

starting-point 是一个列表。例如你可以执行 find dir1 dir2

expression 由测试(Tests)、行为(Actions)、运算符(Operators)等构成。

find 中的测试

测试形如 -testname,通常需要传入参数。对于数值参数 n,传入 +n 代表要求大于 n,-n 代表要求小于 n,n 代表刚好等于 n

-amin n: 最近访问时间距离当前的分钟数
-atime n: 最近访问日期距离今天的天数
-cmin n: 最近一次元信息修改时间距离当前的分钟数
-ctime n: 最近一次元信息修改日期距离今天的天数
-empty: 文件或文件夹为空
-executable:是可执行文件或可穿过的文件夹
-iname pattern:等价于不区分大小写的 -name
-mmin n: 最近一次文件内容修改时间距离当前的分钟数
-mtime n: 最近一次文件内容修改日期距离今天的天数
-name pattern: 用不带路径的文件名匹配 pattern
-path pattern: 用带路径的文件名匹配 pattern
-perm mode: 权限位等于 mode
-size n[cwbkMG]: 文件大小
-type c: 文件类型

注意区分上述选项中的 amin, cmin, mmin。这组区别还在 stat filename 时出现,即对应”access”, “change”, “modify” 三个时间戳:

  • 用 vim 编辑文件内容,三项都会更新
  • 重命名文件,change 会更新
  • 用 cat 列出文件内容,access 会更新
  • 用 echo > file 的方式向文件追加内容,change 和 modify 会更新

总而言之,当调用 read 系统调用读取文件时,根据定义 access 会更新;调用 write 系统调用写入文件内容时,根据定义 modify 会更新,同时由于文件大小这一元信息改变,change 会更新;移动或重命名文件时,change 会更新。

find 中的行为

-delete 删除
-exec command; 执行命令
-printf format 按格式打印

find 举例

find root_path -type d -iname '*lib*' 列出所有文件夹名包含 lib(不区分大小写)的文件夹

find root_path -name '*.py' -not -path '*/site-packages/*' 列出所有 .py 文件,排除路径中有 site-packages 的文件

find root_path -maxdepth 1 -size +500k -size -10M 下降至多一层,找到所有大小在 500k~1M 之间的文件

find . -not -type d -exec wc -l {} \; 对所有文件数行数


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *