命令行处理与版本控制全解析
命令行处理
在使用shell时,命令行处理是一个核心环节,它涉及多个步骤,理解这些步骤对于成为shell脚本编写专家或解决复杂问题至关重要。
命令行处理步骤
每一行从标准输入(STDIN)或脚本中读取的内容被称为管道(pipeline),因为它包含一个或多个由零个或多个管道字符(|)分隔的命令。shell处理命令行的步骤如下:
1.分割为标记:将命令按固定的元字符(空格、制表符、换行符、;、(、)、<、>、| 和 &)分割成标记,标记类型包括单词、关键字、I/O重定向符和分号。
2.检查首个标记:查看每个命令的第一个标记是否为无引号或反斜杠的关键字。如果是如if等控制结构开头的关键字,该命令为复合命令,shell会为其内部设置相关参数,然后读取下一个命令并重新开始处理;若不是复合命令开头的关键字,shell会提示语法错误。
3.检查别名:将每个命令的第一个单词与别名列表进行匹配。若找到匹配项,用别名的定义替换,然后回到步骤1;若未找到,则进入步骤4。此机制允许递归别名,还能为关键字定义别名。
4.花括号扩展:例如a{b,c}会扩展为ab ac。
5.波浪号扩展:若波浪号(~)在单词开头,将其替换为用户的主目录($HOME)。
6.用户主目录替换:将~user替换为用户的主目录。
7.参数替换:对以美元符号($)开头的表达式进行参数(变量)替换。
8.命令替换:对$(string)形式的表达式进行命令替换。
9.算术替换:计算$((string))形式的算术表达式。
10.单词分割:将参数、命令和算术替换后的部分再次分割成单词,这次使用$IFS中的字符作为分隔符。
11.路径名扩展:对出现的*、?和[/]对进行路径名扩展(通配符扩展)。
12.命令查找:按以下顺序查找第一个单词作为命令的来源:函数命令、内置命令、$PATH中任何目录下的可执行文件。
13.执行命令:设置好I/O重定向等操作后执行命令。
以下是一个命令行处理的示例:
假设已经执行了alias ll="ls -l",用户alice的主目录为/home/alice,且有变量$$的值为2537。现在处理命令ll $(type -path cc) ~alice/.*$(($$%1000)),其处理过程如下:
1.ll $(type -path cc) ~alice/.*$(($$%1000))分割输入为单词。
2.ll不是关键字,步骤2无操作。
3.ls -l $(type -path cc) ~alice/.*$(($$%1000))用ls -l替换别名ll,然后重复步骤1 - 3,步骤2将ls -l分割为两个单词。
4.ls -l $(type -path cc) ~alice/.*$(($$%1000))无操作。
5.ls -l $(type -path cc) /home/alice/.*$(($$%1000))将~alice扩展为/home/alice。
6.ls -l $(type -path cc) /home/alice/.*$((2537%1000))用2537替换$$。
7.ls -l /usr/bin/cc /home/alice/.*$((2537%1000))对type -path cc进行命令替换。
8.ls -l /usr/bin/cc /home/alice/.*537计算算术表达式2537%1000。
9.ls -l /usr/bin/cc /home/alice/.*537无操作。
10.ls -l /usr/bin/cc /home/alice/.hist537用文件名替换通配符表达式.*537。
11. 找到命令ls在/usr/bin中。
12. 执行/usr/bin/ls并带上选项-l和两个参数。
命令行处理步骤的流程可以用以下mermaid流程图表示:
graph LR A[分割为标记] --> B[检查首个标记] B --> |非关键字| C[检查别名] B --> |开头关键字| D[处理复合命令] B --> |其他关键字| E[语法错误] C --> |是别名| A C --> |非别名| F[花括号扩展] F --> G[波浪号扩展] G --> H[参数替换] H --> I[命令替换] I --> J[算术替换] J --> K[单词分割] K --> L[路径名扩展] L --> M[命令查找] M --> N[执行命令]引用
引用是让shell跳过部分命令行处理步骤的方法,主要有单引号和双引号两种:
-单引号(’‘):绕过步骤1 - 10,包括别名替换。单引号内的所有字符保持不变,且单引号内不能再使用单引号,即使使用反斜杠转义也不行。
-双引号(”“):绕过步骤1 - 4以及步骤9和10。双引号内忽略管道字符、别名、波浪号替换、通配符扩展和通过分隔符分割单词,但允许参数替换、命令替换和算术表达式计算。可以使用反斜杠转义双引号、$、反引号(`)和反斜杠本身。
以下是引用的示例表格:
| 表达式 | 值 |
| ---- | ---- |
|$person|hatter|
|"$person"|hatter|
|\$person|$person|
|'$person'|$person|
|"'$person'"|’hatter’|
|~alice|/home/alice|
|"~alice"|~alice|
|'~alice'|~alice|
在shell编程中,除非特别需要参数、命令或算术替换,否则使用单引号更安全。
eval命令
eval命令可以让命令行处理过程再次执行,这看似奇怪,但实际上非常强大,它能让脚本动态创建命令字符串并传递给shell执行,使脚本在运行时能修改自身行为。
下面通过示例来理解eval的作用:
- 简单示例:eval ls将字符串 “ls” 传递给shell执行,shell会打印当前目录下的文件列表。
- 复杂示例:
listpage="ls | more" $listpage上述代码不会产生分页的文件列表,因为shell在计算变量时,管道字符在特定步骤之后才被识别,导致|和more被当作ls的参数,ls会报错找不到这些文件名。
而使用eval $listpage时,shell会将ls、|和more作为参数再次进行命令行处理,在步骤2中找到|并将命令分割为ls和more,最终实现分页显示文件列表。
eval是一个高级特性,需要一定的编程技巧才能有效使用,它赋予了程序类似人工智能的能力,能让程序“编写”并执行其他程序,虽然在日常shell编程中可能不常用,但值得深入理解。
版本控制
版本控制系统(Revision Control Systems)不仅能让我们追溯代码的历史,还能查看不同时间点的变更情况,也被称为版本管理系统。它允许我们维护项目文件的中央存储库,跟踪文件的更改及原因,部分系统还支持多人同时协作处理同一项目甚至同一文件。
版本控制系统在现代软件开发中至关重要,同时在文档编写、系统配置跟踪(如/etc)和书籍创作等领域也有广泛应用。其主要优点包括:
1.数据安全:在存储库妥善备份的情况下,能有效防止代码丢失。
2.变更管理:便于实施变更控制,鼓励记录变更原因。
3.多人协作:支持多地人员共同参与项目,避免数据覆盖丢失。
4.多地点工作:允许个人在不同地点工作而不丢失进度。
5.变更追溯:可轻松撤销变更,查看不同版本间的具体差异(二进制文件除外),结合有效日志还能了解变更原因。
6.关键字扩展:通常支持在非二进制文件中嵌入版本元数据。
常见的免费和商业版本控制系统众多,下面介绍三种常见系统:CVS、Subversion和RCS,它们在主流现代操作系统中均可使用。
在使用版本控制系统前,需要确定以下几点:
1.系统选择:决定使用哪种版本控制系统。
2.存储库位置:确定中央存储库的位置(如适用)。
3.项目结构:规划存储库中项目或目录的结构。
4.策略制定:制定更新、提交、标记和分支策略。
CVS(Concurrent Versions System)
CVS是一个广泛使用且成熟的版本控制系统,提供了适用于主流现代操作系统(包括Windows)的命令行工具,部分系统还有GUI工具。
CVS的优缺点
| 优点 | 缺点 |
|---|---|
| 应用广泛且成熟 | 提交操作非原子性,可能导致存储库不一致 |
| 众多Unix管理员和开源开发者熟悉 | 按文件提交,如需引用一组文件需使用标签 |
| 简单项目易于使用 | 目录结构支持较差 |
| 便于访问远程存储库 | 难以在保留历史记录的同时重命名文件和目录 |
| 基于RCS,可对中央存储库进行一定修改 | 对二进制文件和符号链接等对象支持不佳 |
CVS按文件跟踪版本,每个文件有独立的内部版本号,因此单个项目不能用单一版本号跟踪,可使用标签实现此类跟踪。
CVS操作示例
以下是使用CVS的基本操作步骤:
1.创建新存储库:
/home/jp$ mkdir -m 0775 cvsroot /home/jp$ chmod g+srwx cvsroot /home/jp$ cvs -d /home/jp/cvsroot init- 创建新项目并导入:
/home/jp$ cd /tmp /tmp$ mkdir 0700 scripts /tmp$ cd scripts/ /tmp/scripts$ cat << EOF > hello > #!/bin/sh > echo 'Hello World!' > EOF /tmp/scripts$ cvs -d /home/jp/cvsroot import scripts shell_scripts NA- 检出项目并更新:
/tmp/scripts$ cd /home/jp$ cvs -d /home/jp/cvsroot/ checkout scripts /home/jp$ cd scripts /home/jp/scripts$ ls -l- 修改文件并检查状态:
/home/jp/scripts$ echo "Hi Mom..." >> hello /home/jp/scripts$ cvs status /home/jp/scripts$ cvs -qn update- 添加新脚本到版本控制:
/home/jp/scripts$ cat << EOF > mcd > #!/bin/sh > mkdir -p "$1" > cd "$1" > EOF /home/jp/scripts$ cvs add mcd- 提交更改:
/home/jp/scripts$ cvs commit- 更新沙箱、再次修改并查看差异:
/home/jp/scripts$ cvs update /home/jp/scripts$ vi hello /home/jp/scripts$ cvs diff hello- 提交更改并避免使用编辑器:
/home/jp/scripts$ cvs -m '* Fixed syntax error' commit- 查看文件历史:
/home/jp/scripts$ cvs log hello- 添加版本元数据并提交:
/home/jp/scripts$ vi hello /home/jp/scripts$ cat hello #!/bin/sh $Id$ echo 'Hello World!' echo 'Hi Mom...' /home/jp/scripts$ cvs ci -m'Added ID keyword' helloCVS的基本操作流程可以用以下mermaid流程图表示:
graph LR A[创建存储库] --> B[创建项目并导入] B --> C[检出项目] C --> D[修改文件] D --> E[检查状态] E --> F[添加新文件] F --> G[提交更改] G --> H[更新沙箱] H --> I[再次修改] I --> J[查看差异] J --> G G --> K[查看文件历史] K --> L[添加元数据] L --> G通过以上介绍,我们了解了命令行处理的详细步骤、引用和eval命令的使用,以及版本控制系统的重要性和CVS的基本操作,这些知识对于提高编程效率和项目管理能力具有重要意义。