Shells

Shell 基础与常用命令

Shell Types

echo $SHELL #查看当前使用的是什么 shell

  1. Bourne Shell (sh):良好的输入/输出控制,常用于脚本;不适合交互式用户;默认提示为 $
  2. C Shell (csh):invented by Bill Joy;语法和 C 语言类似;输入输出不如 sh;对交互式用户更为友好;工作控制、历史记录等新功能;默认提示为 %
  3. Bourne-Again Shell (bash):GNU 开发的自由软件;提供 csh 的所有交互式特征;编程语言和 sh 兼容;默认提示是 $,root是#

Wild card(通配符)

符号名称作用匹配机制示例
*星号匹配 0 到无穷个字符文件匹配 (Glob)*.log
?问号匹配确切的 1 个字符文件匹配 (Glob)test?.py
方括号匹配指定范围内的 1 个字符文件匹配 (Glob)a-z*
{}花括号生成任意字符串序列字符串扩展 (Expansion){a,b}.txt
~波浪号当前用户的主目录路径扩展~/Desktop

Standard File Descriptors

  • (0) stdin:给程序的标准输入,通常来自键盘,也可以重定向来自文件或命令
  • (1) stdout:来自程序的标准输出,通常输出到终端屏幕,也可以重定向到文件或命令
  • (2) stderr标准错误输出,通常输出到终端屏幕,也可以重定向到文件或命令

File Redirection | 文件重定向

  1. 重定向标准输入
    • < 文件重定向输入 ,从infile读入作为输入 command < infile
  2. 重定向标准输出
    1. 重定向覆盖输出
      • > 重定向覆盖)标准输出到文件 ,不输出到终端屏幕,输出到outfile command > outfile
    2. 重定向追加输出
      • >> 追加标准输出到文件, 不覆盖,写到文件末尾 command >> outfile
  3. 重定向标准错误输出
    1. 2> file 重定向标准错误到文件,2代表stderr
    • 这一块不同shell各不相同,相见本节下小节
  4. 管道符与命令| tee
    • | #命令 1 的标准输出作为命令 2 的标准输入,也就是把command1stdout接到command2stdin command1 | command2
    • tee #复制标准输出:从标准输入读,再写入标准输出和文件(三通)(replicate the standard output)
      • 举例: ls -l | tee lslist
        • 此时 tee 会在屏幕上打印 ls -l 的内容,并将结果写入 lslist
        • -a 选项为追加append,不覆盖,追加到文件的末尾
        • 可以通过 tee test1.txt test2.txt 来写入两个文件,这样会在屏幕上打印(两次应该是版本问题)
  5. 丢弃输出 | To discard output
    • > /dev/null:将输出送到垃圾箱(黑洞)

标准输出流与标准错误输出流

Csh中标准输出流与标准错误输出流

  1. >& file 重定向(覆盖)标准输出和标准错误到文件,&意思可以理解为带上错误
  2. >>& file 追加标准输出和标准错误到文件,与上一点相同
  3. |& command 输送标准输出和标准错误到命令,也就是,把stdoutstderr一起通过管道送给下一个命令
  4. (command > outfile) >& errfile 重定向标准输出和标准错误到不同的文件
    • 第一步:command > outfile
      • 把 stdout(1) 写到 outfile
      • stderr(2) 仍然默认到屏幕
    • 第二步:外层的 >& errfile
      • 它作用的是整个括号里的“命令组”的输出环境
      • 把“剩余的输出”(主要是 stderr)也重定向到 errfile

这些命令在 bash 中也适用

Sh/bash中标准输出流与标准错误输出流

  1. 2> file 重定向标准错误到文件,2代表stderr
  2. > file 2>&1 重定向标准输出和标准错误到文件
    • 拆开来理解(类似于C语言指针的思想
      1. > file是指重定向标准输出>到文件file
      2. 2>处理标准错误输出
      3. &1,不是文件1,而是stdout
      4. 所以2>&1的意思是:让 stderr(2)指向 stdout(1)当前指向的地方
    • bash/zsh 里有一个更短的语法糖&> file,等价于这个,即
    bash
    command &> file
    command > file 2>&1
    
    • 语法糖(Syntactic Sugar): 就是"更甜的语法",让你写起来更简单、更爽,但底层做的事情是一样的。
  3. >> file 2>&1 追加标准输出和标准错误到文件,和上面的区别就是>>
  4. 2>&1 | command 输送标准输出和标准错误到某个命令command
  5. command > outfile 2> errfile #重定向标准输出和标准错误到不同的文件
    • > 代表正常的输出stdout,写到outfile
    • 2>代表错误输出stderr,写到errfile
警告

>& &> 区别

  • 2>&1 的含义:将标准错误输出重定向到标准输出。注意:符号 >& 是一个整体,不可分开,分开后就不是上述含义了。可以比对以下例子进行理解:
    1. cat test > file 2>&1
      • > file 先执行,stdout 指向 file
      • 2>&1 后执行,stderr 指向 stdout 的当前位置(即 file)
      • 结果:stdout 和 stderr 都输出到 file
    2. cat test 2>&1 > file
      • 2>&1 先执行,此时 stdout 还指向终端,所以 stderr 也指向终端
      • file 后执行,stdout 改为指向 file,但 stderr 已经固定指向终端
      • 结果:stderr 输出到终端,stdout 输出到 file(分离了!)
  • 注意与 &> 的区别,&>是在 bash/zsh 里一个更短的语法糖——也就是约定俗成的把stdout stderror一起重定向了,将标准错误和标准输出全部送到一个地方
语法符号作用 (重定向 STDOUT 和 STDERR)Csh / TcshBashSh (POSIX)
>& file将标准输出和错误都写入文件原生支持 (常用)支持 (兼容写法)不支持 (通常报错或行为不同)
&> file将标准输出和错误都写入文件不支持原生支持 (推荐语法糖)不支持

Other Special Command Symbols

  • ; # 命令分隔符,就是cmd1 ; cmd2cmd1执行完之后执行cmd2,哪怕cmd1执行失败
  • & # 在后台运行命令cmd &
  • && #只有上一条命令成功完成才运行下一条命令,逻辑与,make && ./a.out编译成功才运行程序
  • || #只有上一条命令没有成功完成才运行下一条命令,比如:cd /some/dir || echo "no such dir"
  • () #圆括号中的分组子命令在子 shell 中执行
bash
(cd /tmp; ls)
pwd   # 你会发现外面还是原来的目录

Quoting

  • \ #对待下一个字符为纯文本,比如echo \$HOME,输出的是$HOME而不是实际的HOME
  • ' ' #单引号内不允许变量和命令替代,即 $ 失效,就是按字符串对待
  • " " #双引号内允许变量和命令替代,不在字符串中禁用 $ \,和单引号区分一下
  • command$(command) #用命令的输出代替命令输入
bash
echo "today is $(date)"

Job Control

  • & #命令在后台执行
  • ^Z #当正在运行时停止该工作,该工作在后台挂起
  • ^C #结束当前工作
  • bg #继续在后台挂起的工作 bg = back-ground,注意是在后台继续
    • bg + 任务号
  • fg [%n] #将工作返回到前台(sh被堵住了)fg = fore-ground
    • fg + 任务号,这里的任务号都是 jobs 中的,不是 ps 进程号
  • jobs #列出后台工作(list background jobs)
    • -l 列出 pid l = list
    • -r 正在后台 run 的工作 r = run
    • -s 后台中暂停的工作 s = stop
  • ps 可以查看进程状态,top 进行动态进程监控,而 jobs 可以查看后台任务

ps/top 看的是系统层面的进程

jobs 看的是当前这个 shell 启动并管理的任务

[1]+ 27335 Running xeyes &

  • 1是任务号
  • 27335是进程号

Process Commands

ps

  • ps [options] #report a photo snapshot of the current processes, show status of process,显示进程状态
    • -ef # e=every process f=full format使用标准语法查看系统上的每个进程,即显示进程详细信息并显示进程父子关系

UID PID PPID C STIME TTY TIME CMD

root 1 0 0 14:19 ? 00:00:03 /sbin/init splash

root 2 0 0 14:19 ? 00:00:00 kthreadd

root 3 2 0 14:19 ? 00:00:00 pool_workqueue_release

root 4 2 0 14:19 ? 00:00:00 kworker/R-rcu_gp

root 5 2 0 14:19 ? 00:00:00 kworker/R-sync_wq

PPID(Parent Process ID) PID ( Process ID )

  • -l # long format,显示长信息

top

  • top #display Linux processes,显示更新的进程信息和系统使用,间隔为 5s

pstree

  • pstree #显示进程之间的层次关系(show the hierarchical relations between processes)

uptime

  • uptime #显示该系统的运行时长(tell how long the system has been running)

kill

  • kill #terminate or signal processes,终止或示意进程
    • kill [-signal] processID
    • kill -l #列出可用的 kill 信号
    • kill -9 processID 强制杀死进程

Environment Variables

先搞懂两类变量:local vs environment

对应sz的 export PATH=$PAT

Shell 变量(local shell variable)

  • 只在当前这个 shell里有效
  • 你开一个新终端或启动一个子进程,它不一定能看到

例子(bash):

bash
name=Alice
echo $name

环境变量(environment variable)

  • 会被“导出(export)”,变成环境的一部分
  • 启动子进程(比如运行 pythongrepbash)时,子进程会继承这些变量
  • 这就是为什么它叫 environment:它是程序运行的“环境”

例子(bash):

bash
export NAME=Alice

一句话记忆

  • local:只给自己用
  • env:给自己 + 子进程用
  • Set global environment variable
    • setenv NAME value #csh设置环境变量的方式
    • NAME=value; export NAME #sh设置环境变量
    • export NAME=value #bash(export 用于将 shell 变量输出为环境变量)
  • Set local shell variable
    • set name=value #csh设置shell变量
    • name=value #sh/bash设置shell变量
  • Unset
    • unsetenv / unset #csh取消变量
    • unset #sh/bash取消变量
  • Showing/Getting Variables
    • env #csh,bash 也可,只看环境变量
    • set #sh/bash,但 set 会显示更多,因为显示所有变量,env 只是环境变量
    • echo $NAME echo + $变量名 = 打印变量值
  • 当给环境变量赋多个值时,需要用 : 间隔不同值 #When assigning multiple values to the variable they must be separated by the colon : character

典型就是 PATH

bash
echo $PATH
/usr/local/bin:/usr/bin:/bin:...
  • :分割多个目录,shell 查找命令时,会按 PATH 列表顺序去这些目录找

Shell Controls

if 条件

  • 最基本的用法,[ ] 单括号为判断,也叫测试,注意[ <-左右两边要有空格-> ]if then elif else fi 的格式,记忆一下格式即可
bash
 if [ $grade -gt 90 ]; then
     echo "good"
 elif [ $grade -gt 70 ]; then
     echo "pass"
 else
     echo "no"
 fi

options 选项

bash
#这些 -r/-w/-x/... 都是用来判断 某个文件/路径的属性
[ -r filename ]   # 可读?
[ -d dirname ]    # 是目录?
  • -r #存在且可读 true if it exists and is readable, otherwise return false (0)
  • -w #存在且可写 true if it exists and is writable
  • -x #存在且可执行 true if it exists and is executable
  • -f #存在且是常规文件 true if it exists and is a regular file
  • -d #存在且是目录 true if it exists and is a directory
  • -e #存在 true if the file exists
  • -o #使用者拥有该文件 true if the user owns the file
  • -z #文件大小为 0 / 文件为空 true if the file has zero length (empty)

比较运算

  • 只能用于数字:-gtgreater than)、-ltless than)、-eqequal)、-nenot equal)、-leless or equal)、-gegreater or equal)
  • -aand)、-oor)、!(not)
    • [] 中不能直接使用 &&||,只能使用 -a-o!;但在 [] 外面是可以使用的,如:
      bash
      if [ -f $line ] && [ -x $line ]; then
          code...
      else
          code...
      fi
      

[[ ]] 的使用方法

Bash

Zsh

Ksh (Korn Shell)

这三者才兼容这个语法

  • [[ ]] 中使用 >< 等符号不需要转义文字,即>不会被当成重定向,但是注意>比较的是字符串不是数字,数字比较要用-gt -lt,如:
    bash
    if [[ $1 > 5 ]]; then
        echo "$1 的值大于 5"
    else
        echo "$1 的值小于 5"
    fi
    
  • 支持 &&|| 的括号内使用,如:
    bash
     if [[ $a != 3 && $a != 10 ]]; then
         echo "hello i am linux"
     fi
    
  • 如果不使用 [[ ]] 的话则需要这样写:
    bash
     if [ $a != 3 -a $a != 10 ]; then
         echo "hello i am linux"
     fi
    
  • 或者
    bash
     if [ $a != 3 ] && [ $a != 10 ]; then
         echo "hello i am linux"
     fi 
    

for 循环语句

基本用法

for 变量名 in 取值列表; do ... done,如下

bash
for Host in $(seq 1 254); do
     code...
 done
 for i in $(find / -name $filename); do
     code...
 done
 for i in {1..5}; do
     code...
 done 

进阶用法:

如果想依次处理传入程序的参数,可以使用 for $* 的结合

  • $1/$2/$3... 是什么?

当你运行:

bash
 ./demo a b c d
  • $0 = 脚本名(demo)
  • $1 = a
  • $2 = b
  • $3 = c
  • $4 = d 记忆:$N = 第 N 个参数,超过10要加${10}
  • $1:脚本中第一个参数,$2$3$4 以此类推
  • $*:以一个字符串显示所有向脚本传递的参数,等于 "$1 $2 $3 ..."
  • 示例:假设脚本叫 demo.bash,终端输入 ./demo a b c d,则参数依次为 $1 $2 $3 $4,脚本内容如下:
bash
#!/bin/bash

 for i in $*; do
     echo "$i"
 done
  • 这样会依次遍历 $1 $2 $3 $4 并处理(这里是打印到屏幕上)

img

while 循环语句

基本用法:

while 表达式; do 内容 done

  • 可以与 breakcontinue 结合,和C语言类似的
    • break 是终止循环
    • continue 是跳出当前循环
  • 例子如下:在死循环中,满足条件终止循环
bash
 while true; do
     let N++
     if [ $N -eq 5 ]; then
         break
     fi
     echo $N
 done
  • 又如:利用 continue 跳过一次循环
bash
N=0
 while [ $N -lt 5 ]; do
     let N++
     if [ $N -eq 3 ]; then
         continue
     fi
     echo $N
 done

结果输出:1 2 4

进阶技巧:

利用 while 依次读入文件内容进行处理

bash
while read line; do
     echo "$line"
 done < temp.txt
  • line 是一个变量名,这里 while 将依次读取 temp.txt 每行的内容,并打印
  • 这个技巧同上面的 for 进阶技巧一样,非常好用且重要:可以把要处理的内容保存到文本文件中,再用这个 while 逐行处理

case 选择语句

基本用法:

case 变量 in 模式1) ... ;; 模式2) ... ;; esac

bash
case 模式名 in
 模式1)
     命令
     ;;
 模式2)
     命令
     ;;
esac
  • 每个模式必须以右括号 ) 结束(可以是数字或英文)
  • 每个分支的命令必须以双分号 ;; 结束;;就相当于break
  • esac 表示 case 结束
  • 示例:
bash
case $choice in
 1)
     操作
     ;;
 Start)
     操作
     ;;
esac

终端命令与 History

  • 终端:定义指的是与计算机系统相连的一种输入输出设备
    • tty #print the file name of the terminal connected to standard input
  • tty = tell terminal type/device

显示当前终端设备的文件名

  • stty #change and print terminal line settings,改变终端设置
  • stty = set tty
  • History #GNU history library
    • history [nn] #显示过去 nn 条命令
    • !! #重复上一条命令
    • !nn #重复history中编号为 nn 的命令
    • !string #重复最近的以 string 开头的命令

Magic Symbol #!

脚本解释器,告诉系统:这个脚本应该用哪个解释器来运行

  • sh/bash
    • #!/bin/sh
    • #!/bin/bash
  • csh
    • #!/bin/csh -f-f option,without parsing .cshrc
  • 如果没有 Magic Symbol 指示,那么执行脚本命令前必须加上对应的解释器
    • sh sh_script / csh csh_script / bash bash_script / app_prog app_script
  • 但在加上 Magic Symbol 并且 chmod +x script 后,我们即可直接运行脚本
    • sh_script / csh_script / bash_script / app_script

Meta-Characters And REP

Meta-Characters as Wild Cards

这些是 shell 里的通配符(wildcards / globbing),主要用于 lscpmvrmfind(注意:find 本身也有通配/正则的用法)等命令里做文件名/路径匹配

Meta-Character Rules

  • ? 匹配当前位置任意一个字符,即当前位置必须有一个字符(不多不少)
    • ls m?n #m4n, man, m!n
  • * 匹配当前位置任意 0 个或多个字符,即当前位置可以匹配 0 个或多个字符
    • ls * #不是 . 开头的任意文件
    • ls [a-z]* #小写字母开头的任意文件
    • a* 匹配:aababc.txta___
    • *.c 匹配:main.ctest.c
  • [abc] #匹配括号内的任意一个字符
  • [a-d] #匹配括号内范围的任意字符
  • [!abc] #匹配任意字符不在封闭集内
    • ls chpt[1-4] #chpt1, chpt2, chpt3, chpt4
  • {abc,bcd,cde} #匹配任一一组中的任一字符,注意用逗号分隔,花括号展开
    • echo {a,b,c} 会展开成:echo a b c
    • ls {src,include}/*.h 等价于:ls src/*.h include/*.h
  • ~ #当前用户的 home 目录
  • ~user #指定 user 的 home 目录
  • \ #对待下一个字符为纯文本

Regular Expression Syntax

这些是正则表达式(Regular Expression, regex)的基础语法,和通配符很像,但含义不一样,主要给“文本内容匹配”用

REP Rules

  • cat #字符串 cat
  • . #匹配除换行以外的任意单个字符
    • .at #cat, rat, mat, bat
  • ? #匹配之前单个字符的 0 个或 1 个
    • colou?r匹配:color(u 出现 0 次)、colour(u 出现 1 次)
  • * #匹配之前的单个字符的 0 或更多实例
    • xy*z匹配:xz(y 0 次)、xyz(1 次)、xyyz(2 次)、xyyyz(3 次)......
    • [A-Z][A-Z]* #A, CD, GPIO,第一位必须是大写字母,后面可以跟 0 或更多个大写字母
    • [A-Z]* # , A, CD,0 或更多个大写字母
  • [abc] #匹配括号内的任意一个字符
    • gr[ae]y匹配:graygrey
    • [cC]at #cat 或 Cat
  • [a-d] #匹配括号内范围的任意一个字符
  • [^abc] #匹配任意字符不在封闭集内
    • [^a-zA-Z] #任意非字母字符
  • ^exp #以该表达式开头的行
    • ^cat #cat 在行首
  • exp$ #以该表达式结尾的行
    • cat$ #cat 在行尾
    • [0-9]$ #以数字结尾的行
  • \ #对待下一个字符为纯文本
    • \* #对待为纯文本星号 通配符(Wildcards/Globbing)主要用于管理文件,而正则表达式(Regular Expressions)主要用于处理文本内容。

特殊字符总结

正则中的特殊字符(需要转义来匹配字面意思)

text
. * + ? ^ $ { } [ ] ( ) | \

转义序列(需要反斜杠来表示特殊含义)

text
\s  空白字符
\d  数字
\w  单词字符
\n  换行
\t  制表符
  • | #表示或

来个具体例子

bash
❯ ls | grep -E '\(Answer format: "[^"]+"\)'                                  
(Answer format: "edlin, edit")

匹配过程:

  1. " → 匹配第一个双引号 "
  2. [^"]+ → 捕获 edlin, edit (所有非引号字符)
  3. " → 匹配第二个双引号 "
  4. \) → 匹配右括号 )

条件判断部分

第一行包含三个逻辑判断,通常用于决定是否执行后续的 diff 或拷贝操作:

  • (if -d):
    • 通常用于检查某个路径是否存在且是一个目录(Directory)。
    • 完整写法一般是 if [ -d $DIR_PATH ]
  • (if !-w):
    • 检查文件或目录是否不可写! 是取反符号,-w 代表可写(Writable)。
    • 这通常是为了确认目标位置是否有权限限制,或者是否需要提升权限。
  • if [ \tty != "console" ]:
    • 检查当前的终端类型。
    • tty 命令会返回当前控制台的设备名。
    • 意思如果当前用户不是在物理控制台(Console)上直接操作(例如是通过 SSH 远程登录),则条件成立。

命令执行部分

第二行是一个文件对比命令:

diff $SYNOPSYS/admin/setup/$f $LIBX11 >& /dev/null

  • diff: 比较两个文件的差异。
    • 源文件:$SYNOPSYS/admin/setup/$f(通常是 Synopsys EDA 软件的安装配置模板)。
    • 目标文件:$LIBX11(系统路径下的配置文件)。
  • >& /dev/null:
    • 这是一个重定向操作。
    • 它将“标准输出”和“标准错误”全部丢弃到 /dev/null(黑洞)。
    • 目的:脚本并不想让你看到具体的差异内容,它只是通过 diff 的**退出状态码(Exit Status)**来判断两个文件是否一致。
      • 如果状态码为 0:文件完全一致。
      • 如果状态码为 1:文件有差异。
Vi(详细版)
Network

评论区