概述
- kernel => 内核 => 负责和硬件交互
- Shell => 壳 => 和 kernel 交互 => 广义的 Shell 包含一切通过字符终端控制计算机的方式
- Windows => cmd/PowerShell/Git bash
- UNIX/Linux => sh/zsh/Terminal
本质
- 命令行/终端的本质是一个进程
- 在服务器上
cat /etc/passwd
=> root:x:0:0:root:/root:/bin/bash => 最后的 /bin/bash
就是登陆了之后运行哪个程序
- 读取你的输入,并且做出反应
- 内置命令,直接执行
- 查找 $PATH 中对应可执行程序并将参数传递给它
- 如果是 exit,终止自己
- 大多数情况下,执行一个命令 == fork 一个新的进程
- source => 在当前 shell 中执行
- exec => 将 fork 的进程替换为参数的进程 =>
exec echo "123"
=> 暴力的将当前进程替换为 echo 进程
命令的四要素
- 如果四要素完全相同,那么命令一定可以重现
- 如果命令出现诡异问题,那么一定是四要素之一发生了改变
- 四要素
- Executable => 可执行程序
- Arguments => 参数
- Working Directory => 工作路径
- Environment Variable => 环境变量
Environment Variable
- 和进程相绑定的一组键值对
- 所有的操作系统都支持 => Windows 上不区分大小写
- 所有的编程语言都提供原生支持
- 派生的子进程都会继承父进程所有的环境变量 => 不能逆向传递
- Java 命令会读取 CLASSPATH 环境变量,作用相当于 -cp
- 快速传递一个环境变量 =>
AAA=2 source 1.sh
=> 1.sh(echo $AAA)
Executable
- 可执行程序
- Windows => exe/bat/com
- UNIX => +x
- Shell 如何找到可执行程序
- 内置命令
- alias
- $PATH
- Windows 会在当前目录里找
分类
- 二进制可执行程序 => 符合对应操作系统的要求,能够直接执行,无需解释器 =>
file jdk-11.0.13.jdk/Contents/Home/bin/java
=> jdk-11.0.13.jdk/Contents/Home/bin/java: Mach-O 64-bit executable x86_64
- 脚本 => 在第一行指定解释器(shebang) =>
#!/usr/bin/env xxx
(在当前环境变量中查找 xxx 程序) => 等同于 xxx <file>.sh
Arguments
- 参数完全由对应的可执行程序负责解析
- Shell 操作参数
- 通配符展开(globbing)
- 变量展开 => Shell 中的变量($) & $() & ``
- 双引号会进行变量展开,单引号不会
- Shell 获取参数 =>
- $0 => command
- $1 $2 $3 ...
- $@ 将所有参数装配成数组
Working Directory
- 启动命令的路径 => pwd => print working directory
- 相对路径都是相对于这个路径
- Java => new File(".") 代表相对当前目录的路径
标准输入 & 标准输出 & 标准错误
- 文件描述符 => file descriptor
- 标准输入 stdin => fd=0 => 使用 < 或者 <<
- 使用 < 从文件读取数据输入给进程(不一定要是字符) =>
grep 4 < 1.txt
(在 1.txt 中查找4)
- 使用 << 输入块数据
- 标准输出 stdout => fd=1 => 使用 > 或者 >>
- 标准错误 stderr => fd=2 => 使用 2> 或者 2>>
- 标准输出和标准错误输出到同一文件 =>
<command> > output.txt 2>&1
- UNIX 垃圾桶 =>
<command> > output.txt 2> /dev/null
管道
- 连接标准输出和标准输入,将上一个进程的标准输出作为下一个进程的标准输入
- ls | grep class | wc -l => word count -line
- cat 1.txt | grep 3 | xargs touch
- echo ${PATH} | cut -d ':' -f 5
- ps aux | grep java | awk '{print $2}' | xargs kill -9
重定向
- mvn -X compile | tee output.txt
tee
Shell 返回值
- echo $? => 获取上一个进程的返回值
- 每个进程都有返回值
- 0 => success
- 非0 => fail
在 Java 程序中启动和执行命令
- java 命令 =>
java <JVM 启动参数> <Main类> <传给 Main 类的参数>
- Shell 传递的参数会进入 String[] args
- 环境变量 =>
System.getenv("AAA");
=> 只能读不能写
- System Property(系统属性)只有 JVM 支持 =>
System.setProperty("BBB", "111"); System.getProperty("BBB");
=> 可读可写
- -Dfile.encoding=UTF-8 => 指定了系统中默认的编码
JVM Default System Property
Java 程序 fork 一个新进程
import java.io.BufferedReader;
import java.io.InputStream;
public class ForkProcess {
public static void main(String[] args) {
// 获取当前项目的 git log 数据
// executable git
// arguments log --oneline
// environment variable
// working directory
ProcessBuilder pb = new ProcessBuilder("git", "log", "--oneline");
// pb.inheritIO().start();
Process process = pb.start();
InputStream inputStream = process.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, Charsets.UTF_8));
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
pb.environment().put("AAA", "111");
ProcessBuilder pb = new ProcessBuilder("ls", "*"); // Error。此时 * 不会进行通配符展开,通配符展开是 bash 的行为
ProcessBuilder pb = new ProcessBuilder("bash", "-c", "ls *");
ProcessBuilder pb = new ProcessBuilder("ls", "`pwd`/.."); // Error
ProcessBuilder pb = new ProcessBuilder("ls", "|", "wc", "-l"); // Error
}
}
输入输出重定向
- 可能需要额外的线程
- 如果 fork 的进程需要输入才能输出,那么 JVM 和 fork 的进程不能同时进行读写,需要创建一个 write 线程去负责输入,之后 JVM 才能正确读取 fork 进程的输出
知识点
- CR/LF => CarriageReturn/LineFeed
- OS(操作系统) => 抹平了硬件的差异
- POSIX标准
- PS1 => terminal 的前缀提示符
Personal-Laptop-14 at ~ ❯
=> echo $PS1
- bash doc
- bash -x <command> => 将命令内部的执行过程打印出来用于排查
网友评论