diff --git a/CLI/ExifTool/README.md b/CLI/ExifTool/README.md new file mode 100644 index 0000000..dfcab58 --- /dev/null +++ b/CLI/ExifTool/README.md @@ -0,0 +1,17 @@ +# [ExifTool](https://exiftool.org/) 处理图片元信息 + +Window 上的 png 格式文件不支持“标题”、“作者”、“备注”等元信息,但 jpg 格式可以。 + +之所以会找到 ExifTool 这个工具,是因为我想要在 png 格式文件上添加“备注”信息。但最终发现即使你在图片上添加了“备注”信息,Window 也不会显示,所以 ExitTool 的使用应该到此为止了。 + +## 案例 + +```sh +$ exiftool.exe a.png +# 查看 a.png 图片的元信息 + +$ exiftool.exe -XPComment="this is a Comment" a.jpg +# 修改 XP Comment 元信息。虽然 tag name 中有空格,但是在使用时直接忽略空格即可,不要用引号之类的将空格包裹住。 +# 此外,通过命令行的方式似乎不支持中文……。 +# 虽然可以用这种方式为 .png 图片添加元信息,但 Window 上并不会显示。 +``` diff --git a/CLI/FFmpeg/Example.md b/CLI/FFmpeg/Example.md new file mode 100644 index 0000000..56fa9e4 --- /dev/null +++ b/CLI/FFmpeg/Example.md @@ -0,0 +1,217 @@ + + + +# 积累的 FFmpeg 使用案例 + +案例中的说明会尽量简单一点,从而尽可能确保新手也能看懂。这也意味着某些选项的解释可能不够严谨。 + +## `ffprobe` 获取音频总时长 + +```sh +ffprobe -v error -select_streams a:0 -show_entries format=duration -of default=nw=1:nk=1 -sexagesimal +``` + +说明: + +- `-v error` + - 控制消息输出级别为 `error`。在该级别下,info 类型的消息不会输出。banner(包括版权、版本、依赖库相关消息)也不会输出。如果想要输出 info 而不输出 banner,可以使用 `-hide_banner` 选项。 +- `-select_streams a:0` + - `a` 表示音频流,冒号后面的 `0` 表示第一个音频流。 +- `-show_entries format=duration` + - `-show_entries` 包含输入文件的所有条目信息。 + - `format` 表示输入文件的格式信息,这些信息会包裹在 `[FORMAT]`(表头) 和 `[/FORMAT]`(表尾)之间。 + - `duration` 表示输入文件的格式信息中的时长信息,也就是所谓的 key。 +- `-of default=nw=1:nk=1` + - `-of default=` 用于指定默认条目打印的方式 + - `nw=1` 是 `noprint_wrappers` 的简写,表示不显示表头和表尾。 + - `nk=1` 是 `nokey` 的简写,表示不显示 key。 +- `-sexagesimal` + - 没有这个选项时,输出的时长会将是一个数字,比如这样 `275.043265`。 + - 该选项的作用就是将这个数字以 `HH:MM:SS.MS` 的方式输出,比如这样 `0:04:35.043265`。 + +## `ffmpeg` 剪辑音频 + +剪辑音频时,我们一般会需要音频的总时长信息。但某些音频的时长和实际的时长可能是对不上的,这个时候就需要先对音频进行重新编码。命令如下: + +```sh +ffmpeg -y -v error -i +``` + +说明: + +- `-y` + - 当输出文件 `` 已经存在时,会询问是否要覆盖。 + - 该选项的作用就是:不需要询问,直接覆盖。 +- `-i ` + - 用于指定一个输入文件,也就是我们要处理的音频文件所在路径。 +- `` + - 指定输出文件路径。注意不能直接指定为输入文件。如果想要直接覆盖,需要先输出为另外一个文件,然后删除旧文件,重命名新文件。 +- 由于没有指定任何编码选项,默认就是会重新编码。具体编码为什么格式取决于 `` 的后缀名。 + +剪辑音频其实就是指定一个时间段。这就需要两个值:开始时间和结束时间。开始时间可以通过 `-ss` 或者 `-sseof` 指定,结束时间可以通过 `-to` 指定。此外还提供了持续时间,通过 `-t` 指定。FFmpeg 中时间的流向始终是从小到大的,所以开始时间不能大于结束时间,否则会报错或者直接忽略结束时间。下面是一些案例: + +```sh +$ ffmpeg -y -hide_banner -to 10 -i input.mp3 output.mp3 +# 该命令没有提供开始时间,故使用默认开始时间是 0。结束时间指定为 10,表示第 10 秒。 +# 最终会输出第 0 秒到第 10 秒的音频信息到 output.MP3 文件中。 + +$ ffmpeg -y -hide_banner -ss 5 -to 10 -i input.mp3 output.mp3 +# 指定了开始时间为第 5 秒 +# 最终会输出第 5 秒到第 10 秒的音频信息到 output.MP3 文件中。 +# 注意:如果 -to 的值小于 -ss 的值,则会报错。 + +$ ffmpeg -y -hide_banner -ss 5 -t 10 -i input.mp3 output.mp3 +# 指定了持续时间为 10 秒 +# 最终会输出第 5 秒到第 15 秒的音频信息到 output.MP3 文件中 + +$ ffmpeg -y -hide_banner -sseof -20 -t 10 -i input.mp3 output.mp3 +# -sseof 后面必须是一个负数,表示倒数第几秒。-20 就是倒数第 20 秒 +# 最终会输出倒数第 20 秒到倒数第 10 秒的音频信息到 output.MP3 文件中 + +$ ffmpeg -y -hide_banner -sseof -20 -to 60 -i input.mp3 output.mp3 +# 假设 input.MP3 的总时长为 70 秒。 +# -sseof -20 表示开始时间为倒数 20 秒,也就是第 50 秒 +# -to 60 表示结束时间为第 60 秒。 +# 最终会输出第 50 秒到第 60 秒的音频信息到 output.MP3 文件中 +# 注意:如果 -to 后面的数值改为 40,那么 -to 40 选项将会被忽略,最终输出第 50 秒到第 70 秒的音频信息。 +``` + +如果你自己运行了上面的命令,你会发现处理速度似乎很慢,这是因为前面的命令明确声明编码方式,那么 FFmpeg 默认就会重新编码,这就是速度慢的原因。想要让速度变快,可以告诉 FFmpeg 不要重新编码,也就是直接拷贝原来的。方法是借助 `-c` 选项,该选项是 `-codec` 的简写,语法如下: + +```sh +ffmpeg -i -c copy +``` + +说明: + +- `-c copy` + - 因为 `-c` 后面有 `output_url`,所以此处的 `-c` 表示指定编码方式。如果将 `-c` 写在 `-i ` 前面,则表示指定解码方式。 + - `copy` 是一个特殊的编码格式,表示不会重新编码,直接复制原来的,这样速度更快。 +- 直接复制时会有一些副作用,比如最终的时长会有一些误差,无法压缩文件大小等。(没错,重新编码可以压缩文件大小,并且保证视觉上看不出效果)。 + +## `ffmpeg` 提取视频中的音频进行剪辑 + +提取视频中的音频进行剪辑和 [剪辑音频](#ffmpeg-剪辑音频) 的区别只在于,对视频进行操作时你需要明确指定你要处理的是音频,而不是视频和字幕等内容。也就是选中音频流。 + +想要选中音频流,需要借助 `-map` 选项。该选项功能很强大,具体使用不会在这里细说。简单举一个例子就明白了它的基本使用: + +```sh +$ ffmpeg -hide_banner -i input.mkv -map 0:a:0 output.mp3 +# 0:a:0 中,第一个 0 表示的是第一个输入文件,也就是 input.mkv +# 0:a 的 a 表示第一个输入文件的所有音频流 +# 0:a:0 的第二个 0 表示第一个输入文件的第一个音频流。 +``` + +知道如何选取音频了,那么就可以很方便的提取视频中的音频了,下面的语法表示提取视频中的所有音频如下: + +```sh +ffmpeg -i -map 0:a +``` + +如果只想提取某一段,语法如下: + +```sh +ffmpeg -ss 10 -to 100 -i -map 0:a +``` + +## 压缩视频 mkv 视频(保存字幕) + +`ffmpeg -i input.mkv -map 0 -c:s copy output.mkv` ✔️ +  `-map 0` 选择了所有流,效果是保存了所有的字幕。默认只会保留第一个字幕 +  `-c:s copy` 复制原视频字幕格式,效果是保存字幕为文本格式而不是图片格式 + +## 提取字幕 + +`ffmpeg -i input.mkv -map 0:s:0 01.ass` ✔️ +  `-map 0:s:0` 选择流(第一个视频的第一个字幕) +  注意单个 ass 文件不支持同时保存多个字幕。所以 `ffmpeg -i input.mkv -map 0:s 01.ass` 是无法保存所有字幕的。 + +## mkv 内封字幕 + +```sh +ffmpeg -hide_banner -i in.mp4 -i CHS-ENG.srt -i eng.srt -c:v copy -c:a copy -c:s copy -map 0 -map 1 -map 2 -metadata:s:s:0 language=chi -metadata:s:s:0 title="中英双语字幕" -metadata:s:s:1 language=eng -metadata:s:s:1 title="纯英字幕" out.mkv +``` + +简单说明: + +- language 表示语言标识、比如 eng 标识英文,chi 标识中文。 +- title 是字幕标题,可以作为字幕的简单描述来使用。 +- `-metadata:s:s:1` 中的第一个 s 表示设置字幕的元信息,第二个 s 和后面的数字是用来指定第一个字幕流的。 +- mkv 只是一个盒子,用来存放 mk4 和两个字幕。 + +## 内嵌字幕 + +```sh +ffmpeg -i input.mp4 -vf "subtitles=eng.srt" -c:v libx264 -c:a copy output_with_embedded_subs.mp4 +``` + +⚠️还没试过。先放在这里,以后要用到时容易找。 + +## 提取视频中的帧(图片) + +```sh +ffmpeg -i input.mp4 frames/%3d.png +# frames/%3d.png +# 指定输出位置,需要自己创建 frames 文件夹 +# %3d +# 表示生成连续的编号 001 002 …… +# 默认会将整个视频的帧提取出来。 + +ffmpeg -i input.mp4 -vf fps=1 frames/%3d.png +# -vf fps=1 +# -vf 表示 video filter ,视频过滤 +# fps=1 +# 每秒取 1 帧。 可以自己修改数值: +# fps=10 +# 每秒 10 帧 +# fps=1/60 +# 每分钟 1 帧 + +ffmpeg -i input.mp4 -vf select='between(n,1,10)' frames/%3d.png +# -vf select='between(n,1,10)' +# select 用于指定选取范围。注意:引号不能省略,引号中的内容不能用空格。 +# between(n,1,10) +# n 表示帧; 1,10 表示选取的帧范围。 +# 可以通过 + 号拼接多个范围内的帧: +# 'between(n,0,4)+between(n,20,24)' + +ffmpeg -i input.mp4 -vf select='between(t,0,1)' frames/%3d.png +# between(t,0,1) +# t 表示秒,0,1 表示选取的时间范围。(动画通常是每秒 24 帧) +# 注意单位只能是秒,无法使用 HH:MM:SS 这种格式。 +# 不过,可以这样修改单位: +# between(t*1000, 60000, 70000) +# 毫秒。表示选择视频时间从 60000 毫秒到 70000 毫秒之间的帧。 +# between(t/60, 1, 1.5) +# 分钟。表示选择视频时间从 1 分钟到 1.5 分钟之间的帧。 + +ffmpeg -i input.mp4 -vf select='between(n,20,24)' -fps_mode drop frames/%3d.png +# -fps_mode drop +# 设置匹配视频流的帧率模式。这个能够解决重复帧的问题。 +# 除了 drop,还可以是以下值 +# -fps_mode vfr +# -fps_mode 2 +# 这个选项是用来替代旧版本中的 -vsync 0 配置。 +# -vsync 0 +# 用于输出帧的选项。-vsync 控制同步操作 +# 0 表示使用无同步模式。 +# 这意味着输出的帧不会根据输入的时间戳进行重新排序,而是按照它们在源文件中出现的顺序进行提取。 + +ffmpeg -i input.mp4 -s 480x360 frames/%3d.png +# -s 480x360 +# 指定输出分辨率。 高度和宽度必须是合法值 + +ffmpeg -i input.mp4 -vf "scale=480:-1" frames/%3d.png +# -vf "scale=480:-1" +# -1 表示(高度)自适应利用。 + +``` + +## “兔子洞” + +**还是不要想着使用 GPU 加速了。难搞。** + +- `-preset ` + - `` 可选值有 `ultrafast`, `superfast`, `veryfast`, `faster`, `fast`, `medium`, `slow`, `slower`, `veryslow`, `placebo` 越后执行速度越慢,压缩率越高。 +- `ffmpeg -hwaccels` 查看可用的硬件加速方法 +- `ffmpeg -codecs | findStr cuvid` 查看支持 cuda 的编码器 diff --git a/CLI/FFmpeg/README.md b/CLI/FFmpeg/README.md new file mode 100644 index 0000000..8721929 --- /dev/null +++ b/CLI/FFmpeg/README.md @@ -0,0 +1,468 @@ + + + + +# FFmpeg 基本知识点 + +[FFmpeg](https://ffmpeg.org/about.html) 包含以下三个命令工具 + +- `ffmpeg` 用于处理多媒体资源 +- `ffprobe` 用于分析多媒体相关信息 +- `ffplay` 用于播放多媒体。没用过 + +掌握 ffmpeg 的重点就是理解“流”、知道如何选择流(stream_specifier)。 + +掌握 ffprobe 的重点是 section,知道常见的 section 及其对应的 key。 + +语法说明: + +- 左右没有尖括号的表示原样输入,比如 `ffprobe`, `ffmpeg` +- 命令行中,由尖括号包裹着的,表示变量,一般是数字或者字符串。特别的变量有格式要求,一般会进行说明,比如 ``。容易理解的变量不会进行特殊说明,比如 `` 表示输入文件的路径。`` 表示输入,相当于 `-i `。 +- 命令行中,除了 `()`, `[]`, `<>` 符号,其他符号都是需要原样输入的,比如 `:`, `=`, `,`, `-`, `.` 等。 +- 具体情况具体分析。没有什么是百分百正确的。 + +阅读说明:由于不是系统的学习 FFmpeg,而是遇到需求了才去官方文档查找。所以下面笔记的内容不一定连贯。当看不懂时先继续往下看。如果还看不懂,或者没有想要的内容,欢迎创建一个 issue 来询问。 + +## FFmpeg 的处理流程简介 + +ffmpeg 常规转换流程如下: + +1. 提取 input 的信息 +2. 解码(耗时) +3. 转码(耗时) +4. 将处理后的信息输出到 output + +```draw + _______ ______________ +| | | | +| input | demuxer | encoded data | decoder +| file | ---------> | packets | -----+ +|_______| |______________| | + v + _________ + | | + | decoded | + | frames | + |_________| + ________ ______________ | +| | | | | +| output | <-------- | encoded data | <----+ +| file | muxer | packets | encoder +|________| |______________| +``` + +通过流拷贝可以加快速度,此时转换流程如下: + +```draw + _______ ______________ ________ +| | | | | | +| input | demuxer | encoded data | muxer | output | +| file | ---------> | packets | -------> | file | +|_______| |______________| |________| +``` + +## `ffprobe` 和 `ffmpeg` 通用的选项 + +### `` 格式说明 + +命令语法中的 `` 表示“流选择”。常见的 `` 有下面几种格式: + +- `` + - 流的索引值。通过 `ffprobe -show_entries stream=index ` 可以查看 `` 文件中各个流的索引值。 +- `[:]` + - `` 有以下可选值: + - `v`(视频流) + - `a`(音频流) + - `s`(字幕流) + - `d`(数据流) + - `t`(附件流) + - `` 是数字,表示某一类型流中的第几个。从 0 开始计数。 + - 举例说明: + - `a` 选择所有的音频流 + - `a:0` 选择第一个音频流 + - `s:1` 选择第二个字幕流 +- 还有其他的格式,但不用过,所以不记录。 + +### 消息输出 + +- `-hide_banner` + - 执行命令时默认会先输出 banner 信息,banner 中包括其版权、版本、配置、依赖包等相关信息。 + - 该选项就是用来关闭 banner 的输出的。 +- `-v ` + - 终端输出只有两种 —— 标准输出和标准错误输出。这很明显是不够用的。所以 FFmpeg 有自己的一套消息级别。不必要的消息会被输出到标准错误输出中。也就是说,标准错误输出的内容不一定是报错信息。 + - `-v` 是 `-loglevel` 的简写。 + - `` 可以是关键字,也可以是数字,数字是从 -8 开始,依次递增 8。 + - `quiet` 或 `-8` + - 不输出任何多余信息。即只会输出“标准输出”。 + - `panic` 或 `0` + - 目前没有用处。不过其数字形式刚好是 0。 + - `fatal` 或 `8` + - 只显示致命错误信息,这意味着程序无法继续执行。 + - `error` 或 `16` + - 显示所有报错。推荐设置为这个。 + - `warning` 或 `24` + - 显示所有警告信息。众所周知,警告不影响程序执行。 + - `info` 或 `32` + - 这是默认值。输入文件的相关信息、banner 都属于该级别。 + - `verbose` 或 `40` + - 和 `info` 一样 + - `debug` 或 `48` + - `trace` 或 `56` + - 一般使用 `-v error` + +### 时间相关选项 + +常用的有下面四个选项: + +- `-t (/)` +- `-to (/)` +- `-ss (/)` +- `-sseof ()` + +`` 和 `` 的格式必须是 [time duration 格式](#time-duration-格式)。 + +想要确定一段时间,需要以下信息: + +- 开始时间 + - 通过 `-ss` 或者 `-sseof` 指定开始时间点。 + - 如果没有指定开始时间,则默认从 0 开始。 +- 结束时间 + - 通过 `-to` 指定结束时间点。 +- 持续时间 + - 通过 `-t` 指定持续时间。 + - `-t` 参数和 `-to` 参数是互斥的。如果这两个选项都没有配置,则默认的结束时间是文件末尾。 + +注意:大部分格式是无法准确找到某某时间点的,具体参考下面的 `-ss` 中的说明 + +- `-ss (/)` + - 指定开始时间点 + - `` 不能为负数。因为 `` 是相对于文件开头进行计算的。比如 `-ss 10` 表示开头第 10s 左右。 + - 大部分格式文件都是不能精确定位到 `` 时间点。FFmpeg 定位到的时间点称为 seek point, seek point 会在 `` 前面。 + - 当重新编码时,如果配置了 `-accuracy_seek` 选项(这是默认配置),则会将 seek point 和 `` 之间的内容解码然后丢弃。 + - 当进行流复制,或者配置了 `-noaccurate_seek` 选项时,则会将 seek point 和 `` 之间的内容解码并保留。 + +- `-sseof ()` + - `` 只能为负数。因为 `` 是相对于文件末尾进行计算的。比如 `-sseof -10` 表示倒数第 10s 左右。 + - 注意:`-sseof` 指定的是开始时间点。所以不要以为 `-sseof -10 -to 10` 会从第 10 秒开始,在倒数第 10 秒结束。实际情况是从倒数第 10 秒开始,文件末尾结束。 + +- `-t (/)` + - 指定持续时间。`` 不能为负数。 + - 具体是从哪里开始计算持续时间,得看 `-ss` 或者 `-sseof` 的值。默认是从文件开头开始计算。 + - 比如 `-ss 10 -t 5` 表示从第 10 秒左右开始,在第 15 秒左右停止。 + +- `-to (/)` + - 指定在什么时间点停止。`` 不能为负数,因为 `-to` 必须大于 `-ss`。 + - 比如 `-ss 10 -to 5` 会报错。 + - 但 `-to` 和 `-sseof` 一起使用时,如果 `-to` 的时间点在 `-sseof` 之前,则 `-to` 会被忽略,即结束时间为文件末尾。 + +案例: + +```sh +$ ffmpeg -v error -ss 10 -i input.mp3 -c copy o.mp3 +# 从 input.mp3 第 10s 位置开始截取,直到结束。 + +$ ffmpeg -v error -sseof -10 -i input.mp3 -c copy o.mp3 +# 从 input.mp3 的倒数第 10s 秒位置开始截取,直到结束。(即截取最后 10s) + +$ ffmpeg -v error -ss 10 -t 5 -i input.mp3 -c copy o.mp3 +# 从 input.mp3 的第 10s 开始,截取 5s 时长的音频。(最终音频只有 5s 左右) + +$ ffmpeg -hide_banner -ss 10 -t 13 -i input.mp3 -c copy o.mp3 +# 从 input.mp3 的第 10s 开始截取,直到第 13s 时结束。(最终音频只有 3s 左右) +``` + +### time duration 格式 + +只有两种格式。 + +第一种格式: + +```sh +[-][HH:]MM:SS[.m...] +``` + +说明: + +- `-` 为可选,表示负数。 +- `HH` 为可选,表示 时, 必须是两位数。 +- `MM` 为必选,表示 分,必须是两位数。 +- `SS` 为必选,表示 秒,必须是两位数。 +- `.m` 为可选,跟在 `SS` 后面,`m` 前面有个小数点。不要求多少位(一般是一到四位)。 + +第二种格式: + +```sh +[-]S[.m][s|ms|us] +``` + +说明 + +- `-` 为可选,表示负数。 +- `S` 为必选,默认表示的是秒。不要求具体多少位。 +- `.m` 为可选,跟在 `S` 后面,前面有个小数点,不要求多少位(一般是一到四位) +- `s`, `ms`, `us` 为可选,用于指定 `S` 的单位,分别表示 秒、毫秒、微秒。默认是秒。 + +很明显,当语法中有冒号 `:` 时,表示第一个格式,否则表示第二种格式。 + +| 案例值 | 第几种格式 | 说明 | +|------------|--------------------------------|------------------------| +| `24:08` | 第一种格式,指定了 `HH` 和 `MM` | 表示 24 分 8 秒。 | +| `01:30:10` | 第一种格式,指定了 `HH`, `MM`, `SS`, | 表示 1 小时 30 分 10 秒。 | +| `-02:00` | 第一种格式,指定了 `-`, `HH`, `MM` | 表示最后两分钟。 | +| `55` | 第二种格式,并且只指定了 `S`, | 表示 55 秒。 | +| `0.2` | 第二种格式,指定了 `S` 和 `.m`, | 表示 0.2 秒。 | +| `200ms` | 第二种格式,指定了 `S` 和 `ms` | 表示 200 毫秒 | +| `-120s` | 第二种格式,指定了 `-`, `S`, `s` | 表示最后两分钟(120 秒)。 | + +## `ffprobe` + +### `ffprobe` 语法 + +```sh +ffprobe [options] +``` + +### 常见选项 + +- `-select_streams ` + - 选择指定流。`` 格式参考 [Stream specifiers](#stream_specifier-格式说明) + - 案例: + - `-select_streams 0` 选择索引值为 `0` 的流。比如视频文件的第一个流一般是视频流。 + - `-select_streams a:0` 选择第一个音频流。 + +- `-of [=writer_options]` + - `-of` 是 `-print_format` 的简写。 + - `` 表示输出格式,有以下可选值,点击对应值可查看对应的输出案例 + - [`default`](writer_name.md#default) + - [`compact`](writer_name.md#compact) + - [`csv`](writer_name.md#csv) + - [`flat`](writer_name.md#flat) + - [`ini`](writer_name.md#ini) + - [`json`](writer_name.md#json) + - [`xml`](writer_name.md#xml) + +- `-of default=` + - 用于指定默认的信息输出格式。有下面两个配置项: + - `nw` / `noprint_wrappers` + - 默认值为 0,设置为 1 时不输出 section header 和 section footer。 + - `nk` / `nokey` + - 默认为 0,设置为 1 时不输出 key。 + - 举例说明:默认输出的信息格式是下面这样的: + + ```syntax + [SECTION] + key1=val1 + ... + keyN=valN + [/SECTION] + ``` + + - 设置为 `-of default=nw=1` 时不输出 `[SECTION]` 和 `[/SECTION]` + - 设置为 `-of default=nk=1` 时不输出 `key1`, ..., `keyN` + - 设置为 `-of default=nw=1:nk=1` 时只输出 `val1`,...`valN` + - section。常见的有: + - `[STREAM]` 流相关信息。常见的信息有: + - `index` 流的索引值。 + - `codec_type` 流类型,比如 `video`, `audio`, `subtitle`。 + - `channels` 声道数量。 + - `width`, `height`, `coded_width`, `coded_height` 视频的宽高。 + - `[FORMAT]` 文件相关信息,常用的信息有: + - `duration` 时长,比如视频时长或音频时长。 + - `filename` 文件名。 + - `format` 资源的格式,如 mp3, mp4 等。 + - `size` 资源大小,单位字节。 + +show 开头的选项都是用来查看 section 的,比如下面这样: + +- `-show_entries ` + - 学过 js 的都知道,js 中也经常有 key, value, entries。key 和 value 不用说,entries 就是包含 key 和 value。所以 show_entries 就是所有的键值对信息,由于这个信息非常庞大,所以必须指定 ``。 + - entries 可划分为一个个 section, 每一个 section 都有许多 key-value。所以 `` 就是用来指定 section 和 key 的。它的具体语法为 `=,:=,`。相关案例会在下面给出。 +- `-show_streams` 查看 `[STREAM]`。 +- `-show_format` 查看 `[FORMAT]`。 +- `-show_error` 查看 `[ERROR]`(如果有的话)。 +- `-show_data` +- `-show_data_hash ` +- `-show_packets` +- `-show_frames` +- `-show_log ` +- `-show_programs` +- `-show_chapters` 等等。 + +- `-sexagesimal` + - time 类型的输出,默认是普通数字格式,添加该选项能够其转换为 "HH:MM:SS.MS" 格式。 + - 比如 `275.043265` 会变成 `0:04:35.043265`。 + +### `-show_entries ` + +`` 的语法为: + +```sh +=,:=, +``` + +案例值 | 说明 +-----------------------------------------------------------|--------------------------------------- +`-show_entries format` | 等同于 `-show_format`,查看所有 `[FORMAT]` +`-show_entries format=filename` | 查看所有 `[FORMAT]` 中 key 为 `filename` 条目。 +`-show_entries format=filename,size` | 通过逗号可以拼接多个 key +`-show_entries stream` | 等同于 `-show_stream`,查看所有 `[STREAM]` +`-show_entries format=size:stream=index` | 通过冒号 `:` 可以拼接多个 section +`-show_entries format=duration,size:stream=index,channels` | 连用 +`-show_entries format_tags=title,comment` | 获取 TAG 时需要添加 `_tags` 后缀。 + +一般会再结合 `-of [=writer_options]` 来省略一些冗余信息。比如下面这样 + +```sh +$ ffprobe -v error -show_entries format=duration input.mp3 +[FORMAT] +duration=341.968980 +[/FORMAT] + +$ ffprobe -v error -show_entries format=duration -of default=nw=1 input.mp3 +duration=341.968980 + +$ ffprobe -v error -show_entries format=duration -of default=nk=1 input.mp3 +[FORMAT] +341.968980 +[/FORMAT] + +$ ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 input.mp3 +341.968980 + +``` + +## `ffmpeg` + +### `ffmpeg` 语法 + +```sh +ffmpeg [] {[] -i } ... {[] } ... +``` + +说明: + +- 顺序很重要。 +- 允许指定多个输入文件,所有的输入文件必须在输出文件之前。当出现多个输入文件时,通过输入顺序(从 0 开始)指定对应的输入文件 +- options 应用在下一个指定的文件。所以,全局配置必须写在输入文件配置前面,即 `` 必须在 `` 前面 + +### 流选择和流控制的默认行为 + +ffmpeg 默认会根据以下规则选择流: + +- 视频流:选取最高分辨率的视频流 +- 音频流:选取包含最多通道的音频流 +- 字幕流:只选取第一个字幕流。比如简繁两种字幕,则只会选择第一个。 +- 数据流:不会被选择 +- 附件流:不会被选择 + +流控制,指的的解码和编码。ffmpeg 默认会按照以下规则处理流: + +- 视频流:会重新编码,耗时长 +- 音频流:会重新编码,耗时长 +- 字幕流:重新编码为图片,而不是文本。图片会导致字幕变得不清晰,但好处是不需要额外的字体文件。 +- 由于数据流和附件流默认不会被选择,所以也不存在默认流控制行为。 + +当提供 `-map` 配置项时,将会变成手动选择流。`-map` 的值由三部分组成:`::` + +举例说明: + +```text +input file 'input.mkv' + stream 0: 视频流,分辨率 1920x1080 + stream 1: 音频流 2 channels + stream 2: 简体字幕 + stream 3: audio 5.1 channels + stream 4: 繁体字幕 +``` + +`-map 0` 则选择第一个输入的所有流(包括视频音频字幕) +`-map 0:s:0` 则选择第一个输入的第一个字幕流(即简体字幕),此时不会选择视频和音频。这可用于提取字幕文件 `ffmpeg -i A.mp4 -map 0:s:0 output_subtitle.ass` +`-map 0:a` 表示选择所有的音频流,此时不会包含视频和字幕。 +如果想要选择多个,可以指定多个 `-map`。比如 `ffmpeg -i input.mkv -map 0:v -map 0:s -c:v copy -c:s copy output.mkv` 不选择音频 + +通过 `-codec: ` 手动控制流。 `-codec` 可简写为 `-c`,如下: + +### 全局选项 + +- `-y` +  输出文件已存在时直接覆盖 +- `-n` +  输出文件已存在时直接退出。 + +### 常用选项 + +- `-map [-][:][?] ()` + - `-` 表示不选择(取反)。 + - `` 表示第几个输入文件,从 0 开始计数。 + - `` 用来指定流, + - `?` 表示可选,当不存在该流时直接忽略,而不是报错。 + +案例值 | 说明 +--------------|-------------------------------- +`-map 0` | 表示选择第一个输入文件的所有流。 +`-map 1:0` | 表示选择第二个输入文件的第一个流。 +`-map 0:s:0` | 表示选择第一个输入文件的第一个字幕流。 +`-map 0:s?` | 表示选择第一个输入文件的所有字幕流。如果没有字幕流的话则忽略。 +`-map -0:a:1` | 表示不选择第一个文件的第二个音频流。 + +- `-c[:] (/)` + - `-c` 是 `-codec` 的简写。 + - 当后面是 `` 时,`` 表示输入文件的解码方式(``)。 + - 当后面是 `` 时,`` 表示输出文件的编码方式(``)。 + - 如果不想重新编码,可以指定 `` 为 `copy`。这样运行速度更快。 + - 案例: + - `ffmpeg -i input.mkv -map 0 -c:v libx264 -c:a copy output.mkv` + - `-map 0` 可以省略,原因在于只有一个输入文件。 + - `-c:v libx264` 后面跟的是输出文件,所以选项表示输出时,将所有视频流重新编码为 libx264 格式。 + - `-c:a copy` 输出时,不会重新编码所有音频流。 + - `ffmpeg -i input.mkv -map 0 -c copy -c:v:1 libx264 -c:a:1 libvorbis output.mkv` + - `-c copy -c:v:1 libx264` 除了第二个视频流会被重新编码为 libx264 外,其他视频流都不会重新编码 + - `-c:a:1 libvorbis` 对第二个音频流重新编码为 libvorbis 格式。 + - `-c:v ` 指定输出视频的编码格式,比如 + - `-c:v copy` 表示重新编码,速度更快。 + - `-c:v libx264` 表示先解码,然后编码为 `libx264`。(根据流程的描述,我认为:即使原本编码格式就是 `libx264`,那么也会重新编码) + - `-c:a ` 指定输出音频的编码格式 + - `-c:s ` 执行字幕流编码格式。字幕格式有两种:文本或者图片。一般都是使用文本而不是图片。编码为图片的好处是不需要字体文件。 + +- `-vcodec ()` + - 等同 `-c:v `(或 `-codec:v `) +- `-acodec ()` + - 等同 `-c:a `(或 `-codec:a `) +- `-scodec ()` + - 等同 `-c:s `(或 `-codec:s `) + +- `-vn (/)` + - 不选择所有视频流。 +- `-an (/)` + - 不选择所有音频流。 +- `-sn (/)` + - 不选择所有字幕流。 +- `-dn (/)` + - 不选择所有数据流。 + +- `-fs ()` + 限制输出的文件大小,单位是字节 + +- `-metadata[:] = ()` + - 配置输出文件的元信息。通过设置空值(空字符串)可以删除某个元信息。 + - `` 是可选的,它可以用来设置或覆盖流的元信息。更多细节可以查看 `-map_metadata`。 + - 常见的元信息有: + - `title` + - `language` + - 案例: + - `ffmpeg -i in.mkv -metadata title="my title" out.mkv` + - 设置输出文件的 title + - `ffmpeg -i in.mkv -metadata:s:a:0 language=eng out.mkv` + - 设置第一个字幕流和第一个音频流的语言为英文。 + +- `-frames[:] ()` + - 当帧数达到 `` 后就停止写入。 + - 曾经的选项 `-dframes ()` 已经被废弃了,该选项现在可以用 `-frames:` 代替。 + +- `-attach ()` + - 添加附件。 + - 比如添加字幕文件: `ffmpeg -i in.mkv -attach DejaVuSans.ttf -metadata:s:0 mimetype=application/x-truetype-font out.mkv` +- `-dump_attachment[:] ()` + - 导出并移除附件。 + - 比如导出字体文件,并重命名为 `out.ttf` 文件。`ffmpeg -dump_attachment:t:0 out.ttf -i in.mkv` + - 比如移除所有附件,文件名同元信息中的文件名相同。`ffmpeg -dump_attachment:t "" -i in.mkv` diff --git a/CLI/FFmpeg/ache.md b/CLI/FFmpeg/ache.md new file mode 100644 index 0000000..775bdeb --- /dev/null +++ b/CLI/FFmpeg/ache.md @@ -0,0 +1,62 @@ +# 临时笔记区 + +## 乱码 + +在 Window 系统上,通过文件新建为 `in2.mp4` 视频添加“备注”信息为“一”。 + +### 操作一 + +分别在三个终端(powershell, bash, cmd)上执行命令 `ffprobe -v error -show_entries format_tags=comment in2.mp4`,结果均显示乱码,显示内容如下: + +```text +[FORMAT] +TAG:comment=涓€ +[/FORMAT] +``` + +### 操作二 + +分别在三个终端上执行同样的操作,但这次将输出重定向到文件中,命令为:`ffprobe -v error -show_entries format_tags=comment in2.mp4 > tmp` 此时: + +- powershell 输出的内容如下,文件编码为 UTF-16LE,此时会乱码 + + ```text + [FORMAT] + TAG:comment=涓€ + [/FORMAT] + + ``` + +- bash 输出的文件内容如下,编码为 UTF-8,不会乱码 + + ```text + [FORMAT] + TAG:comment=一 + [/FORMAT] + + ``` + +- cmd 输出文件内容如下,编码为 UTF-8,不会乱码(效果同 bash) + + ```text + [FORMAT] + TAG:comment=一 + [/FORMAT] + + ``` + +### 操作三 + +- powershell + - 先执行 `chcp 65001` + - 然后执行操作一,终端上不输出乱码了。 + - 执行操作二,依旧是 UTF-16LE 编码,并且乱码。 +- cmd + - 先执行 `chcp 65001` + - 然后执行操作一,终端上不输出乱码了。 + - 执行操作二,依旧是 UTF-8 编码,并且不会乱码。 + +- bash + - 不管是执行 `export LANG="en_US.GB2312"`,还是 `export LANG="en_US.GBK"` 最终控制台的输出都是乱码。文件的输出都是 UTF-8, 没有乱码。 + +此外,ffprobe 似乎无法获取图片的元信息(至少目前为止我没找到),只能获取视频或音频的。 diff --git a/CLI/FFmpeg/writer_name.md b/CLI/FFmpeg/writer_name.md new file mode 100644 index 0000000..750f31e --- /dev/null +++ b/CLI/FFmpeg/writer_name.md @@ -0,0 +1,120 @@ + + +# 查看 `` 的输出效果 + +以输出文件格式信息(`-show_format`)为例 + +## default + +```sh +$ ffprobe -v error -show_format input.mp3 +# 等同 ffprobe -v error -show_format -of default input.mp3 +[FORMAT] # 这个称为 section header +filename=input.mp3 # 这些就是 key=value +nb_streams=1 +nb_programs=0 +format_name=mp3 +format_long_name=MP2/3 (MPEG audio layer 2/3) +start_time=0.025057 +duration=341.968980 +size=2735977 +bit_rate=64005 +probe_score=51 +TAG:encoder=Lavf60.3.100 +[/FORMAT] # 这个称为 section footer + +``` + +## compact + +```sh +$ ffprobe -v error -show_format -of compact input.mp3 +format|filename=input.mp3|nb_streams=1|nb_programs=0|format_name=mp3|format_long_name=MP2/3 (MPEG audio layer 2/3)|start_time=0.025057|duration=341.968980|size=2735977|bit_rate=64005|probe_score=51|tag:encoder=Lavf60.3.100 + +``` + +## csv + +```sh +$ ffprobe -v error -show_format -of csv input.mp3 +format,input.mp3,1,0,mp3,MP2/3 (MPEG audio layer 2/3),0.025057,341.968980,2735977,64005,51,Lavf60.3.100 + +``` + +## flat + +```sh +$ ffprobe -v error -show_format -of flat input.mp3 +format.filename="input.mp3" +format.nb_streams=1 +format.nb_programs=0 +format.format_name="mp3" +format.format_long_name="MP2/3 (MPEG audio layer 2/3)" +format.start_time="0.025057" +format.duration="341.968980" +format.size="2735977" +format.bit_rate="64005" +format.probe_score=51 +format.tags.encoder="Lavf60.3.100" + +``` + +## ini + +```sh +$ ffprobe -v error -show_format -of ini input.mp3 +# ffprobe output + +[format] +filename=input.mp3 +nb_streams=1 +nb_programs=0 +format_name=mp3 +format_long_name=MP2/3 (MPEG audio layer 2/3) +start_time=0.025057 +duration=341.968980 +size=2735977 +bit_rate=64005 +probe_score=51 + +[format.tags] +encoder=Lavf60.3.100 + +``` + +## json + +```sh +$ ffprobe -v error -show_format -of json input.mp3 +{ + "format": { + "filename": "input.mp3", + "nb_streams": 1, + "nb_programs": 0, + "format_name": "mp3", + "format_long_name": "MP2/3 (MPEG audio layer 2/3)", + "start_time": "0.025057", + "duration": "341.968980", + "size": "2735977", + "bit_rate": "64005", + "probe_score": 51, + "tags": { + "encoder": "Lavf60.3.100" + } + } +} + +``` + +## xml + +```sh +$ ffprobe -v error -show_format -of xml input.mp3 + + + + + + + +``` diff --git a/CLI/ImageMagick/example.md b/CLI/ImageMagick/example.md new file mode 100644 index 0000000..3523a30 --- /dev/null +++ b/CLI/ImageMagick/example.md @@ -0,0 +1,21 @@ +# 案例 + +## 将多张图片拼接为 gif + +```sh +magick convert -delay 100 -loop 0 -dispose previous frames/*.png animated.gif +# convert +# 执行转换操作 +# -delay 100 +# 每帧之间的延迟时间。 单位是毫秒 ms。最小值为 10 +# -loop 0 +# gif 动画重复次数。 0 表示永远 +# -dispose previous +# 在绘制下一帧之前,清除前一个帧。 +# frames/*.png +# 待拼接的图片,按名称进行排序,比如 1.png 2.png 3.png +# animated.gif +# 生成的图片。 +# 注意: gif 格式保存的图片清晰度肯定是不如原始图片的。 + +``` diff --git a/CLI/README.md b/CLI/README.md new file mode 100644 index 0000000..42f9f8f --- /dev/null +++ b/CLI/README.md @@ -0,0 +1,18 @@ +# 个人笔记仓库之 CLI 系列 + +TODO: + +- 搭建自己的命令搜索引擎网页,并将所有命令相关内容进行迁移! + + +文件夹介绍: + +- [git](https://git-scm.com/): 分布式版本管理工具 +- win: window 相关(cmd,pwsh,pwsh7) +- shell: 非 window 相关(linux) +- [node](https://docs.npmjs.com/): 与 nodejs 相关(npm, package.json) +- [FFmpeg](https://ffmpeg.org): 处理视频音频等多媒体文件 +- [ImageMagick](https://imagemagick.org/): 处理图片 +- [ExifTool](https://exiftool.org): 处理图片元信息 + +--- diff --git a/CLI/git/README.md b/CLI/git/README.md new file mode 100644 index 0000000..03c1db9 --- /dev/null +++ b/CLI/git/README.md @@ -0,0 +1,220 @@ +# git 笔记 + +TODO: + +- 等待重构。我发现 git 官网所提供的 [book](https://git-scm.com/book/en/v2) 写的非常好,结构也非常好。所以我打算跟着它的结构进行笔记的整理。 + +git book 中值得整理的文章内容,实用性较高的内容: + +- [第一章:介绍](https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control) +- [第二章:基操](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository) +- [第三章:分支](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) +- [第六章:Github](https://git-scm.com/book/en/v2/GitHub-Account-Setup-and-Configuration) +- [第七章:工具](https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection) 这里面有很多有用的知识点能帮助我更好的理解 git 的相关命令的使用 +- [第十章:原理](https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain) +- [附录:嵌入 git](https://git-scm.com/book/en/v2/Appendix-B%3A-Embedding-Git-in-your-Applications-Command-line-Git) +- [附录:命令](https://git-scm.com/book/en/v2/Appendix-C%3A-Git-Commands-Setup-and-Config) + +## git 常用命令 + +```sh +$ git (help | -h) +# 给出 git 常用命令,并且会给出学习 git 上的相关帮助 + +$ git -h +# 调出对应命令的手册页面。 +# 比如 git -h git, git -h status, git -h revert +``` + +命令目录: + +- git 的初始化与配置 + - [init](#init) + - [config](#config) +- git 的基本操作。 + - 暂存与提交 + - [add](./reference/basic.md#add) + - [stage](./reference/basic.md#stage) + - [mv](./reference/basic.md#mv) + - [rm](./reference/basic.md#rm) + - [restore](./reference/basic.md#restore) + - [commit](./reference/basic.md#commit) + - 分支 + - [branch](./reference/basic.md#branch) + - [tag](./reference/basic.md#tag) + - [checkout](./reference/basic.md#checkout) + - [switch](./reference/basic.md#switch) + - [合并](./reference//basic.md#合并策略) + - [merge](./reference/basic.md#merge) + - [rebase](./reference/basic.md#rebase) + - [cherry-pick](./reference/basic.md#cherry-pick) + - 撤销 + - [reset](./reference/basic.md#reset) + - [revert](./reference/basic.md#revert) +- git 管理云端仓库 + - [clone](./reference/remote.md#clone) + - [fetch](./reference/remote.md#fetch) + - [pull](./reference/remote.md#pull) + - [push](./reference/remote.md#push) + - [remote](./reference/remote.md#remote) +- git 检查历史和状态 + - [bisect](./reference/examine.md#bisect) + - [diff](./reference/examine.md#diff) + - [grep](./reference/examine.md#grep) + - [log](./reference/examine.md#log) + - [show](./reference/examine.md#show) + - [status](./reference/examine.md#status) +- 其他零碎内容 + - [show-ref](./reference/other.md#show-ref) + - [shortlog](./reference/other.md#shortlog) + - [blame](./reference/other.md#blame) + - [reflog](./reference/other.md#reflog) + - [whatchanged](./reference/other.md#whatchanged) + - [stash](./reference/other.md#stash) + - [ls-remote](./reference/other.md#ls-remote) + - [submodule](./reference/other.md) + +## git初始化与配置 + +### init + +```sh +$ git init +# 将当前目录初始化为一个 Git 仓库。 + +$ git init +# 创建一个新的文件夹,并将其初始化为一个 Git 仓库。 + +$ git init --bare [ | .] +# 初始化为 bare repo(裸仓库)。 +# bare repo 通常用于云端仓库,它不包含 work tree 和 index。 +``` + +### config + +Get and set repository or global options. You can query/set/replace/unset options with this command. The name is actually the section and the key separated by a dot, and the value will be escaped. + +语法如下: + +```sh +git config +``` + +配置文件有四类,优先级按以下顺序依次递增: + +- `--system` + - 全系统的配置文件。目录为 $(prefix)/etc/gitconfig +- `--global` + - 用户特定的配置文件/全局配置文件。目录通常是 ~/.gitconfig +- `--local` + - 仓库特定的配置文件。目录为 .git/config +- `--worktree` + - 可选。需开启 extensions.worktreeConfig 配置才生效。目录为 .git/config.worktree + +常用属性: + +- `--show-origin` + - 输出配置项来源 +- `--unset` + - 删除某项配置 + +```sh +$ git config -l +# 按配置文件优先级从低到高,列出所有配置项。如果指向查看指定某个配置文件,请添加对应参数。 + +$ git config -l --show-origin +# 显示每个配置项的“来源类型 + 实际来源”。 +# 实际来源通常指的是对应的配置文件路径 + +$ git config +# 按配置文件优先级,查找某一配置项的值 + +$ git config +# 按配置文件优先级,修改/新增某个配置项的值 + +$ git config --unset +# 按配置文件优先级,删除某个配置项的值 + +$ git config --edit +# 按配置文件优先级,直接编辑最近的配置文件,通常是 .git/config 文件。 + +$ git config --global http.proxy http://127.0.0.1:7890 +# 只在全局配置文件上新增/修改 http.proxy 配置项 + +$ git config --global --unset http.proxy +# 只在全局配置文件上删除 http.proxy 配置项 + +$ git config --global core.editor 'code -w' +# 使用 vscode 编辑配置文件,而不是 vim。(记得先将 vscode 添加至 path) +# 注意这里要使用 code -w ,原因是 git 的很多操作都是通过判断 hint editor +# 是否关闭,来决定是否进入下一步操作的。所以我们需要添加 --wait 参数来 +# 使编辑器处于等待模式。 + +$ git config --global --unset core.editor +# 恢复默认 vim 编辑配置文件 + +git config --global core.excludesFile "$Env:USERPROFILE\.gitignore_global" +# 添加全局 git 忽略文件。文件需自己创建。通常指定: node_modules 和 desktop.ini + +git config --global http.version HTTP/1.1 +# 解决下面报错(在 WSL 中) +# error: RPC failed; curl 16 Error in the HTTP2 framing layer +# fatal: expected flush after ref listing + +git config core.autocrlf +# true: 在提交时将换行符转换为 LF,在检出时将 LF 转换为 CRLF。相当于为所有文件自动设置 core.eol = text +# input:不进行任何转换。 + +git config --global init.defaultBranch main +# 更改默认分支名称为 main +``` + +## 概念 + +### revisions + +git 命令中最常用的参数(没有之一)就是和 revisions 有关。详见 [`git -h revisions`](https://git-scm.com/docs/gitrevisions) + +#### 有关提交 commit 那些事 + +当我使用中文“提交”的时候,含义是一个名词,表示某次提交;当我使用英文 commit 的时候,表示的是动词,表示的是 git commit 这个动作。一段哈希值、一个分支名、一个标签名、特殊标识符号 HEAD(表示当前位置)等都可用于表示某个特定的提交。通常都会使用 C1 <—— C2 <—— C3 等等来表示某段提交,注意箭头指向的是父节点(旧 <—— 新)。 + +在操作分支之前,了解一些 HEAD 的概念是非常有帮助的。HEAD 是一个特殊的表示,表示当前位置。他可以指向某段哈希值/标签,或者引用某个分支。可以通过 `cat .git/HEAD` 查看具体的 HEAD 值,或者通过 `git symbolic-ref HEAD` 查看对应的引用(如果有的话) + +`reset`, `revert` 需要接收某个提交作为参数,表示回到该提交,其含义是回到该提交所作出的变更并未 commit 的时候。比如 `git revert C2` 表示回到 C2,此时 C1 已经 commit,但 C2 所作出的变更还在等待 commit,此时如果有冲突需要先处理冲突才可以 commit。而 `checkout` 命令也需要接收一个提交作为参数,但它的含义是回到该提交已 commit 的时候,比如 `git checkout C2`,此时 C2 已经 commit,没有任何需要 commit 的变更内容。 + +#### 引用(refs) + +git 中每次提交的本质是哈希值,但哈希值不方便,于是有了引用。分支、标签、HEAD 其实都是属于一种引用。很多命令都需要我们提供一个 `` 参数,这个参数表示的其实就是哈希值或引用,通过 `git show-ref` 可查看所有引用。 + +此外还有相对引用,主要有下面两种: + +- `~` 符号表示向上移动 `` 个提交记录。 + - 如果不指定 ``,则向上移动一个提交记录 +- `^` 符号表示向上移动一个提交记录。 + - 该符号后面也可以跟随数字,当只有当一个提交的有多个提交记录时才有意义。 + - 比如合并后的提交记录就有两个父提交。此时 `^` 指的是直接父提交,`^2` 指的就是被合并的父提交 +- `^` 和 `~` 均可以使用多次,且可以连用, + +#### HEAD 的作用 + +根据前面的只是,我们知道同一个提交,可能有不同的表达方式:哈希值、分支、标签、引用、HEAD等。而我们所能操作的,其实只是 HEAD。 + +HEAD 的含义永远都是当前位置,当我们对分支进行任意操作时,本质上都是通过 HEAD 来执行的: + +- 当我们 checkout/switch 某个分支上时,其实是将 HEAD 该分支绑定。此时,当我们移动 HEAD 时,其对应的分支也会移动。 +- 但当我们 checkout 标签(或哈希值或远程分支)时,HEAD 并不会与其绑定,而是进入分离 HEAD 状态(detached HEAD mode),所以此时我们移动 HEAD 时,标签是不会跟着移动的。 + +### 冲突 + +执行 `merge`, `revert`, `cherry-pick`, `rebase` 等命令时都可能会出现需要手动处理冲突的情况。为方便下面的描述,将执行命令时所在位置称为 head 提交,将出现冲突时所在提交位置称为 target 提交(被合并的提交)。在 vscode 中解决冲突时,Accept Current Change 表示只保留 head 提交中的内容,Accept Incoming Change 表示只保留 target 提交的内容)。 + +执行 `git status` 可以查看有冲突的文件(在 Unmerged paths 下)。 + +- 在解决完冲突文件后,通过 `git add `,标记冲突已被解决,并将所做的更改添加到暂存区。 +- 如果解决冲突后,只想将其标记冲突已解决,但并不想将更改添加到暂存区,则使用 `git restore --staged ` 命令。 +- 如果想直接删除冲突文件,可以执行 `git rm ` 命令,它能删除文件,同时标记冲突已被解决,并将所做的更改添加到暂存区。 + +## 参考 + +- [官方文档 git 术语表,用于理解各种概念!](https://git-scm.com/docs/gitglossary/en) diff --git a/CLI/git/draft.md b/CLI/git/draft.md new file mode 100644 index 0000000..cb27919 --- /dev/null +++ b/CLI/git/draft.md @@ -0,0 +1,121 @@ +# Draft + +原本,我配置的 ignore 是这样的: + +```ignore +src/public/* +``` + +也就是忽略静态资源文件夹。 + +但后面,我希望保留 src/public/js/icon-font.js 这个文件, +于是我自然地添加下面一条规则 + +```diff + src/public/* ++ !src/public/js/icon-font.js +``` + +但没有效果。最终发现,想要不忽略这个文件,必须一层一层写,最终的规则应该是这样的: + +```ignore +# 忽略 public 文件夹 +src/public/* +# 但保留 public/js 文件夹 +!src/public/js/ +# 忽略 public/js 文件夹下的所有文件 +src/public/js/* +# 但保留 public/js/icon-font.js 文件 +!src/public/js/icon-font.js +``` + +--- + +只忽略不以 `bom-` 开头的 csv 文件正确格式: +```ignore +*.csv +!bom-*.csv +``` + +```sh +$ git rev-parse --abbrev-ref HEAD +# 显示当前 HEAD 所在分支名。 +# 如果当前处于 detached HEAD 状态,则只会显示 HEAD +# 如果刚 git init,则显示 HEAD 并报错 +# 如果不在 git 仓库中,无输出并报错 +$ git rev-parse --short HEAD +# 不在分支上时,显示短 hash + +$ git symbolic-ref --short HEAD +# 显示当前 HEAD 所在分支名。 +# 如果刚 git init,则显示默认分支名 +# 如果当前处于 detached HEAD 状态,则无输出并报错 +# 如果不在 git 仓库中,无输出并报错 + +$ git describe --tags --exact-match HEAD +# 显示当前 HEAD 的标签名,如果没有则报错 + +$ git branch --show-current +# 显示当前 HEAD 所在分支 +# 如果刚 git init,则显示默认分支名 +# 如果当前处于 detached HEAD 状态,则无输出 +# 如果不在 git 仓库中,无输出并报错 + +$ git rev-parse --is-inside-work-tree +# 判断当前目录是否在 git 仓库中 +``` + +## refs + +`/refs/tags` +`/refs/heads` +`/refs/remotes` + + +未验证: +- `refs/stash`:这个文件记录了 git stash 命令创建的临时提交的引用。 +- `refs/notes/`:这个目录用于存储 Git Notes,它是一种可以附加在 Git 对象(如提交对象)上的附加信息。 +- `refs/replace/`:这个目录包含了替换对象的引用,它允许用户指定替换规则,将一个对象替换为另一个对象。 + +## git 是通过识别 .git/rebase-merge 文件夹,来判断当前是出于什么状态的 + +比如当你进行 rebase 操作时,如果发现控制台输出下面信息时 + +``` +warning: could not read '.git/rebase-merge/head-name': No such file or directory +``` + +你就应该知道问题出在 .git/rebase-merge 文件夹,虽然你可以简单地通过删除这个文件夹,来强制退出 rebase 状态,但你应该进一步探讨为什么会出现这种情况! + +## 使用 code -w 作为 git 默认编辑器时,出现数据库检测错误 + +``` +[31512:0411/184727.811:ERROR:service_worker_storage.cc(2016)] Failed to delete the database: Database IO error +``` +该错误信息并不影响功能就是了。而且该报错信息可能无法复现,因为我在别人电脑上进行同样操作时,发现他们并不会出现这个报错消息。 + +具体什么原因,我只找到了这个 [issue](https://github.com/electron/electron/issues/35036) + +## 遗留的基础知识(等待解决和整理) + +```sh +# git push -u origin main:tmp +# 这种方式是无法建立追踪关系的,后续继续使用 git push 时也无法成功 +# 会提示: +fatal: The upstream branch of your current branch does not match +the name of your current branch. To push to the upstream branch +on the remote, use + + git push origin HEAD:tmp + +To push to the branch of the same name on the remote, use + + git push origin HEAD + +To choose either option permanently, see push.default in 'git help config'. + +To avoid automatically configuring an upstream branch when its name +won't match the local branch, see option 'simple' of branch.autoSetupMerge +in 'git help config'. + +``` diff --git a/CLI/git/reference/basic.md b/CLI/git/reference/basic.md new file mode 100644 index 0000000..dd3c149 --- /dev/null +++ b/CLI/git/reference/basic.md @@ -0,0 +1,387 @@ +# 提交与分支 + +## add + +Add file contents to the index. + +```sh +$ git add (-A | --all) +# 更新工作目录(working tree)中所有文件所对应的索引(index)。 +# 效果上就是所有变更(changes, 包括 modified, deleted)和新文件(untracked)都被添加到暂存区(temporary staging area)。 +``` + +## stage + +Add file contents to the staging area + +This is a synonym for git-add + +## mv + +Move or rename a file, a directory, or a symlink. + +相比普通的 mv 命令,该命令帮我们省略了 add 命令。 + +## rm + +Remove files from the working tree and from the index + +```sh +$ git rm --cached .. +# 取消对 的追踪,但不从工作目录中实际删除该文件。 + +$ git rm -rf * +# (从 index 和working tree 中)删除当前分支中的所有文件 +``` + +## restore + +Restore working tree files. Restore specified paths in the working tree with some contents from a restore source. + +```sh +git restore ... +# 丢弃文件的变更内容。等同于 vscode 中 Source Control 中的 Changes 中的返回符号(Discard Changes) + +git restore --staged ... +# 将该文件从已归档中移出。等同于 vscode 中 Source Control 中的 Staged Changes 中的减号符号(Unstaged Changes) +``` + +## commit + +Record changes to the repository. + +Create a new commit containing the current contents of the index and the given log message describing the changes. The new commit is a direct child of HEAD, usually the tip of the current branch, and the branch is updated to point to it (unless no branch is associated with the working tree, in which case HEAD is "detached") + +```sh +$ git commit (-a | --all) +# --all 容易误导人,让人以为它的效果和 git add --all 一样。 +# 但实际上 --all 仅仅是将所有变更(modified, deleted)添加暂存区,新创建的文件(untracked)保持不变。 + +$ git commit -m +# Use the given as the commit message. + +$ git commit --amend +# Replace the tip of the current branch by creating a new commit. +# 修改最近一次提交的注释内容。 + +$ git commit --allow-empty +# 创建一个空提交 + +$ git commit -a --amend --no-edit +# 将当前变更内容直接和前一个提交合并,不需要修改提交注释 +``` + +## branch + +List, create, or delete branches + +```sh +$ git branch +# 列出所有本地分支。 + +$ git branch -r +# 列出所有远程分支 + +$ git branch -a +# 列出所有分支(本地和远程) + +$ git branch -vv +# 显示分支的 哈希值(SHA1)、提交的注释信息(commit subject line)和对应的上游分支(upstream branch) + +$ git branch [ | HEAD] +# 从 上创建一个新分支。 + +$ git branch (-m | --move) +# 修改分支名。 + +$ git branch (-d | --delete) +# 删除本地分支。 +# 想要删除云端分支,可以使用 git push origin -d +# 或 git push origin : + + +$ git branch (-f | --force) [ | HEAD] +# 强制将 移动到 。 +# 注意, 不能是当前所在分支。 + +$ git branch (-t | --track | --track=direct) +# 新建一个分支,并让其追踪 +# 注意:git branch -t 的效果是新建一个分支,并将该分支追踪当前位置! + +$ git branch (-u | --set-upstream-to) [ | HEAD] +# set the to track 。 + +$ git branch --unset-upstream +# Remove the upstream information for . +# If no branch is specified it defaults to the current branch. +``` + +## tag + +标签就像是一个锚点,可以把它当成某个提交哈希值的别名。标签不像分支,它无法改变位置,所以标签通常用于记录某个重要的节点,比如某个大版本的发布或者某次重要的合并操作或者修正某个重要的 bug 等。 + +```sh +$ git tag +# 列出所有标签。 + +$ git tag [ | HEAD] +# 给某次提交 添加一个标签。 + +$ git push --tags +# 将本地所有标签推送到云端 + +$ git tag (-d | --delete) +# 删除本地标签。 + +$ git push origin :refs/tags/ +# 删除云端上的标签 + +$ git describe [ | HEAD] +# 查找距离 最近的祖先标签。如果 本身就是标签,则只输出对应标签名。否则输出 __g +# 为标签名。 +# 表示 之间相差多少个提交记录 +# 指的是该标签所对应提交记录的前几位哈希值 + +``` + +## checkout + +Switch branches or restore working tree files + +Updates files in the working tree to match the version in the index or the specified tree. If no pathspec was given, git checkout will also update HEAD to set the specified branch as the current branch. + +checkout(check out) 意为检出。简单理解就是将当前位置(HEAD)移动到对应位置上。 + +checkout 的功能相当强大,从而也显得臃肿和复杂,所以出现了一些新的命令来分担它的相关功能,比如 switch 命令,下面是一些可用 switch 命令替换的功能: + +```sh +$ git checkout +# 切换分支 + +$ git checkout -b [ | HEAD] +# 新建分支并切换到该分支。可指定分支的起始位置 + +$ git checkout -b -t +# 在 处新建分支并切换到该分支,同时将新分支追踪 。 + +$ git checkout --orphan +# 新建一个空分支,并切换到该分支。 +# 空分支:没有任何历史提交记录,和其他分支也没有任何联系 +``` + +checkout 还可以切换到指定的某次提交。但这样描述可能让人觉得和“checkout”的含义没太大关系,所以我们换种描述:checkout 可以从历史提交记录中检出某次提交。不仅如此,checkout 还可以只检出某次提交中的某个文件,效果上就像是将某个文件恢复到之前的某个版本一样! + +```sh +$ git checkout +# 检出某次提交 + +$ git checkout +# 检出 (通常是一个提交) 时 的内容。 +# 上面的命令解释起来太过麻烦,写成这样 git checkout 来解释比较方便 +# 将 的内容恢复到 版本时的状态。 + +$ git checkout (-p | --patch) -- +# 类似于上一个命令,但 -p 参数能够让我们做一些其他操作,而不是直接恢复成某个版本的状态 +``` + +## switch + +Switch to a specified branch. The working tree and the index are updated to match the branch. All new commits will be added to the tip of this branch. + +```sh +$ git switch +# 切换分支 + +$ git switch -c [ | HEAD] +# 新建分支并切换到该分支。可指定分支的起始位置 + +$ git switch -c -t +# 在 处新建分支并切换到该分支,同时将新分支追踪 。 + +$ git switch --orphan +# 新建一个空分支,并切换到该分支 +``` + +## 合并策略 + +默认情况下,git 会自动选择合适的策略,但我们也可以手动指定策略: + +```sh +git (merge | rebase | cherry-pick) --strategy= +# 可选策略有 octopus ours recursive resolve subtree. +``` + +- `ours` 策略 + - 舍弃另一条分支所作出的变更,只保留当前分支。 +- `recursive` 策略 + - 最常见、最常用策略,同时也是合并有交叉分支时 git 的默认策略 + - 算法描述:递归寻找路径最短的唯一共同祖先节点,然后以其为 base 节点进行递归三向合并。 +- FAST-FORWARD MERGE + - 快速合并策略。在该策略下,将两个处于同一条历史提交树的提交合并时,git 不会创建新的提交记录,而是直接移动到最新的提交上。比如有这么一个历史提交树 c1 <—— c2 <—— c3 ,在 c1 上执行 `git merge c3` 或 `git rebase c3` 时,git 会直接将 c1 移动到 c3 上。 + - `git merge` 时添加 `--no-ff` 参数可以阻止此行为,即强制新建一个提交用于合并。 + +## merge + +Join two or more development histories together + +```sh +$ git merge +# 将 合并到当前位置(HEAD),并新建一个提交 + +$ git merge --no-commit +# 合并后不自动 commit + +$ git merge --allow-unrelated-histories +# 允许合并两个不相关(没有共同祖先)的分支 + +$ git merge -s ours +# 与 合并,但不接受 中的任何内容 + +$ git merge --no-ff +# 不采取快速合并(Fast-Forward)策略 +``` + +## rebase + +该命令本质上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。效果是能够让历史提交树变得更加简洁清晰。 + +`rebase` 和 `merge` 的区别就在于: + +- `merge` 会保留所有提交纪录,但历史纪录会分叉; +- `rebase` 能够让历史提交树不分叉,但会丢失提交纪录(垃圾回收机制)。 + +```sh +$ git rebase [ | HEAD] +# 取出 上的一系列提交记录,然后“复制”他们,并按顺序依次添加到 之后。 +# 这个一系列提交记录,指的是 的最近公共父节点之间的一系列提交记录。 +``` + +如果初次接触 rebase,可以进入[该网站](https://learngitbranching.js.org/?NODEMO),然后运行以下命令加强对 rebase 的认识 + +```sh +git switch -c bugfix +git commit +git commit +git switch main +git commit +git commit +git rebase main bugfix # 该命令可拆分为 git switch bugfix 和 git rebase main 两条命令 +``` + +### 交互式 rebase + +进入交互出 rebase 模式时,会打开一个 todo 文件,我们可以在文件中书写等待处理的命令。 + +```sh +$ git rebase (-i | --interactive) +# 交互式处理 HEAD 到 (不包含该提交)之间的一系列提交,然后将其放在 之后。 + +$ git rebase --edit-todo +# 打开 todo 文件,编辑剩余的待处理命令 +``` + +初次进入交互式处理时,todo 文件中会按顺序列出 [HEAD, <提交>) 之间的一系列提交,并且每行都属于一条命令,默认是 `pick `。交互式 rebase 本质上就是从上自选按序运行这一系列命令,结果会得到一系列提交的拷贝,这些拷贝将会放在 `` 之后。所以,我们可以修改这些命令,来实现“交互式 rebase”了。比如更改命令顺序,就相当于更改提交记录的顺序(tips: 在 vim 中按下快捷键 `dd` 可剪切整行,然后按下 `p` 可将整行粘贴到当前行的下面),这类似于 `cherry-pick` 命令,但更加便捷。 + +一些命令的含义如下: + +- `pick `: use commit。 + - 解释:pick 表示仅仅只是选择该提交,而不会做其他操作(git 也不允许我们做其他操作,除非需要解决冲突) +- `reword `: use commit, but edit the commit message + - 解释:reword 同样也是选择该提交,但会停下来修改本次 commit 的注释内容。 +- `edit `: use commit, but stop for amending. + - amending 表示做一些补充工作,在此期间可以选择创建新的提交来记录,也可以创建,而是直接 continue。 + - 当补充工作完成时,执行 `git rebase --continue` 继续执行下一条命令 + - 如果需要修改后续的命令,执行 `git rebase --edit-todo` 可再次编辑 todo 文件 +- `squash `: use commit, but meld into previous commit + - 将该提交直接合并在前一个提交中。但会要求我们重新编写 commit 内容 +- `fixup [-c | -C] ` + - 该命令是 `squash` 的快捷版本,默认(`-c`)只采用前一个记录的 commit 内容。如果指定 `-C` 参数,则只采取当前记录的 commit 内容。 +- `drop `: remove commit + - 相当于 pick 的反义词,表示不选择该提交。 + - 直接将某行删除也是一样的效果。 +- `break`: stop here (continue rebase later with `git rebase --continue`) + - 自我感觉效果上似乎和 `edit` 命令一样。(但 vscode 似乎没有不认为 break 过程是同样处于 rebasing 状态) + +```sh + +$ git rebase --continue +# 继续 rebasing 过程,也就是继续执行 todo 文件中的命令。 + +$ git rebase --skip +# 如果没有冲突,效果上类似于 --continue。 +# 如果出现冲突,则该命令会直接丢弃待合并的提交,继续下一条命令。效果上等于 drop + +$ git rebase --abort +# 取消本次 rebase 操作 +``` + +## cherry-pick + +Apply the changes introduced by some existing commits + +Given one or more existing commits, apply the change each one introduces, recording a new commit for each. + +cherry-pick 只挑选指定提交所带来的变更,然后将其添加在当前位置(HEAD)之后。 + +注意是**指定提交所带来的变更**,并不包含指定提交前的变更总和! + +```sh +$ git cherry-pick -n +# 应用 带来的变更内容,但不创建新的提交 + +$ git cherry-pick ..main +# 将不属于 HEAD,但属于 main 的祖先的一系列提交添加到 HEAD 之后 + +$ git cherry-pick --skip +# 跳过本次提交、不选择本次提交 + +$ git cherry-pick --continue +# (解决冲突后)继续 cherry-pick 过程 + +$ git cherry-pick --abort +# 取消本次 cherry-pick 操作 +``` + +## reset + +Reset current HEAD to the specified state + +reset 可直接回退到某次提交。支持保留/删除被撤销的提交所作出的变更内容。该命令非常好理解,使用时只需注意一点,该命令不同于 revert 和 cherry-pick 等命令,该命令不支持撤回(没有 `--abort` 参数可供选择)。 + +```sh +$ git reset (--soft) HEAD^ +# 回退到上一次提交,即撤销本次的提交,同时保留本次提交的变更内容。(默认) + +$ git reset --hard HEAD^ +# 回退到上一次提交,即撤销本次的提交,同时删除本次提交的变更内容⚠️ + +$ git reset (HEAD) +# Unstage All Changes。 +# 回退到 HEAD 时的状态,其索引(index)也会回退,也就是清空暂存区(git restore --staged *), +``` + +## revert + +该命令用于撤销某次提交所作出的变更。相当于 cherry-pick 的逆操作——选出要撤销的提交,然后将其移除。 + +revert 过程中出现冲突,则表示撤销变更的内容后,与当前的内容有冲突。 + +```sh +$ git revert -n main~2 main~3 main~4 +$ git revert -n main~5..main~2 +# 依次撤销 main~2 main~3 main~4 共三个提交所做出的变更 +# 注意顺序问题 +# +# 每成功撤销一个提交,都会创建一个新的提交来记录本次 revert 操作 +# 通过 -n/--no-commit 可阻止此行为。 + +$ git revert -n main~5^..main~2 +$ git revert -n main~2 main~3 main~4 +# 由于 .. 运算符是一开一闭结构,我们可以直接在左部添加一个 ^ 来实现左右闭合 + +$ git revert --abort +# 取消本次 revert 操作 + +git revert --no-commit HEAD +# revert,但不提交 +``` diff --git a/CLI/git/reference/examine.md b/CLI/git/reference/examine.md new file mode 100644 index 0000000..74323da --- /dev/null +++ b/CLI/git/reference/examine.md @@ -0,0 +1,84 @@ +# 检查历史和状态 + +## bisect + +Use binary search to find the commit that introduced a bug + +## diff + +Show changes between the working tree and the index or a tree, + changes between the index and a tree, + changes between two trees, + changes resulting from a merge, + changes between two blob objects, or + changes between two files on disk. + +```sh +$ git diff --shortstat "@{0 day ago}" +# 简单统计今天的文件变更记录 +``` + +## grep + +Print lines matching a pattern + +## log + +Show commit logs + +```sh +$ git log +# 查看历史提交纪录。 + +$ git log --abbrev-commit --pretty=oneline +# --abbrev-commit 显示短的哈希值 +# --pretty=oneline 代表将内容单行显示,超出部分隐藏。也可写成 --oneline + +$ git log --graph +# 可以看到提交历史的字符图形表示。 + +$ git log --stat +# 显示每次提交的文件变化情况 + +$ git log -5 --oneline +# 显示最近5次提交 + +$ git log --follow --oneline +# 显示 的变更记录 + +$ git log -p +# 显示 每次变更的内容 +``` + +## show + +Show various types of objects(blobs, trees, tags and commits). + +```sh +$ git show --name-only +# 显示某次提交时发生变更的文件 + +$ git show : +# 显示某次提交后,文件 的内容 +``` + +## status + +Show the working tree status + +Displays paths that have differences between the index file and the current HEAD commit, paths that have differences between the working tree and the index file, and paths in the working tree that are not tracked by Git (and are not ignored by gitignore). + +```sh +$ git status +# 注意,若当前分支有上游分支,其所指的上游分支本质上只是本地中的远程分支 +# 故想要查看当前分支与云端上的分支之间的关系,需要先更新(fetch)远程分支 + +$ git status (-s | --short) +# 以简短的形式给出输出。当没有输出时表示没有变更的内容。比如: +# [??] 表示未被追踪 +# [!!] 表示被忽略,需要提供 --ignored 才会显示被忽略的文件 +# [A ] 表示该文件是新建的,并且已被追踪(added to index) +# [ M] 表示该文件已被修改,但还未添加进暂存区(no updated in index) +# [M ] 表示该文件已被修改,并且已添加进暂存区(updated in index) +# [ D] 表示该文件已被删除,但还未添加进暂存区(no updated in index) +``` diff --git a/CLI/git/reference/other.md b/CLI/git/reference/other.md new file mode 100644 index 0000000..f869f7f --- /dev/null +++ b/CLI/git/reference/other.md @@ -0,0 +1,96 @@ +# 其他零碎内容 + +## show-ref + +List references in a local repository + +## shortlog + +```sh +$ git shortlog -sn +# 显示所有提交过的用户,按提交次数排序 +``` + +## blame + +```sh +$ git blame +# 显示 的编辑记录 +``` + +## reflog + +```sh +$ git reflog +# 管理 Reference logs。 +# 在没有可视化的情况下,可以通过该命令来查看某次提交与 HEAD 的距离 +``` + +## whatchanged + +```sh +$ git whatchanged --oneline +# 查看 的变更记录 +``` + +## stash + +Stash the changes in a dirty working directory away + +```sh +$ git stash +# 将当前工作目录中的更改(不包括未必追踪的文件)藏匿起来(移入 dirty working directory) +$ git stash save "message" +# 将当前工作目录中的更改藏匿起来,并附加一条消息。 +$ git stash apply +# 将最新的藏匿恢复到当前工作目录中,但不会从藏匿堆栈中删除它。 +$ git stash pop +# 将最新的藏匿恢复到当前工作目录中,并从藏匿堆栈中删除它。 +# 如果 pop 时出现冲突,则不会自动删除,后续需要你手动通过 git stash drop 删除 +$ git stash list +# 显示当前所有藏匿的更改列表。 +$ git stash drop stash@{n} +# 删除指定的藏匿,其中n是藏匿的索引号,从0开始计数。 +# 也可以写成 git stash drop ${0} +``` + +## ls-remote + +```sh +$ git ls-remote +# List references in a remote repository + +$ git ls-remote -q +# Do not print remote URL to stderr. + + +``` + +## submodule + +```sh + +$ git submodule add +# 添加一个子模块 +# 添加时,默认会克隆对应子模块,不过当别人从云端克隆该仓库时,是不会自动下载子模块中的内容的。 +# 如果想要自动下载子模块内容,可以运行 git clone --recursive +# 或者可以在克隆后的仓库中通过下面两个命令实现同样的效果: +# git submodule init 先初始化 +# git submodule update 然后更新子模块内容 +# 当子模块云端仓库更新时,只需要进入子模块目录,然后 git pull 就可以获取最新的子模块内容了 + +$ git submodule add -b +# 指定指定分支 + +$ git config -f .gitmodules submodule..branch +# 修改子模块中需要对应的分支 + + +$ git submodule init +$ git submodule update + +$ git submodule update --init + +$ git submodule update --remote +# 直接更新所有子模块中的内容到最新提交记录。 +``` diff --git a/CLI/git/reference/remote.md b/CLI/git/reference/remote.md new file mode 100644 index 0000000..36fcb47 --- /dev/null +++ b/CLI/git/reference/remote.md @@ -0,0 +1,193 @@ +# git 与云端的交互 + +通常情况下,本地分支和远程分支的名称是相同的,故为了更好地区分,通常在远程分支前面添加一个远程仓库别名,比如 `origin/`。 + +git 与云端的交互可以概括为两点: + +- 向远程仓库推送数据 +- 从远程仓库拉取数据 + +要与远程仓库进行交互,一个必要的前提就是要让本地分支追踪(track)远程分支。由于这个过程很常见,所以当我们克隆云端仓库到本地时,git 会自动帮我们完成该工作。否则,需要我们显式指定追踪关系——借助 `--track` 和 `--set-upstream-to` 参数。 + +远程交互式,下面几个变量经常使用,所以统一在这里进行解释: + +- `` + - 表示远程仓库,可以是一个 URL 或远程仓库别名(如 origin) +- `` + - 指代远程仓库的引用和本地的引用,格式为 `+:` + - 默认是 `remote..fetch` 配置项。 +- `` + - 远程仓库别名,通常是 origin + +## clone + +Clone a repository into a new directory + +Clones a repository into a newly created directory, creates remote-tracking branches for each branch in the cloned repository (visible using git branch --remotes), and creates and checks out an initial branch that is forked from the cloned repository’s currently active branch. + +```sh +$ git clone [] +# Clone a repository into a new directory +# 当克隆一个本次仓库时,第二个参数不能省略。使用案例: +# git clone D:\tmp\t1 D:\tmp\t2 +# git clone D:\tmp\t\.git t2 + + +$ git clone -b +# 只克隆指定分支。注意分支名要区分大小写 +# 不单单是分支, 的值还可以是一个 hash、一个标签名! + +$ git clone -c http.proxy="127.0.0.1:7890" +# 克隆时显式指定代理 + +$ git clone --depth +# 有时候项目太大,可以进行浅克隆,比如: +# git clone --depth=1 https://github.com/microsoft/TypeScript.git + +$ git clone --shallow-since="1 week" +# 克隆最近一周的 +# 如果提示 fatal: error processing shallow info: 4,则可能是因为对应日期之后,没有提交内容。 + +$ git clone --shallow-since="2023-09-06" +# 克隆 2023-09-06 日之后的 + + +$ git clone --recursive +# 克隆时,同时克隆子模块中的内容 +``` + +## fetch + +Download objects and refs(branches/tags) from another repository. + +fetch 的核心工作: + +- 从远程仓库下载本地仓库中缺失的提交记录 +- 更新远程分支指针(如 origin/main) + +注意,fetch 并不会改变本地仓库的状态、不会更新本地分支,不会修改文件内容。可以将 fetch 理解为单纯的下载操作。想要将下载下来的数据同步到本地仓库中,需要进行合并操作——比如 `cherry-pick`, `rebase`, `merge` 等命令。 + +```sh +$ git fetch +# 拉取远程仓库的所有引用 +$ get fetch +# 拉取远程仓库的 引用 + +$ git fetch origin +# The above command copies all branches from the remote refs/heads/ namespace +# and stores them to the local refs/remotes/origin/ namespace, +# unless the remote..fetch option is used to specify a non-default refspec. + +$ git fetch origin br +# 从云端仓库(origin)中拉取 br 分支。此时远程分支 origin/br 将会被更新(或创建) +# 如果本地没有 br 分支,那么当切换到 br 分支时,将会自动创建该分支,并且建立追踪关系 +$ git fetch origin br:br +# 该命令和前面的命令很像,但有些许区别。 +# 该命令的作用是将远程仓库中的 br 分支拉取到本地 br 分支,但并不会自动设置追踪上游关系 +# 如果本地不存在 br 分支,则会自动创建 br 分支,但该分支并不会自动追踪远程分支 +# 所以当切换到 br 分支时,你的无参数 fetch, pull, push 命令会提示你请先设置上游分支! + +$ git fetch --prune +# git 的默认习性是只有当你显式地指明要删除某些数据,这些数据才会被删除。 +# 所以当远程仓库中的分支或标签被删除时,fetch 操作并不会自动删除本地所对应的分支或标签 +# 慢慢地,本地会累积了很多无用的远程引用,此时就可以通过 prune 命令来剔除无用的远程引用。 +``` + +## pull + +Fetch from and integrate with another repository or a local branch + +Incorporates changes from a remote repository into the current branch. + +pull 虽方便,但当你想将本地的多个副分支合并到云端主分支,并且偏爱 rebase 时,推荐使用 fetch,因为 rebase 支持指定“基”。 + +```sh +$ git pull +# 从远程仓库拉取并合并 + +$ git pull origin br +# 将 origin/br 分支合并到当前位置(HEAD)。相当于以下两条命令: +# git fetch origin +# git merge origin/br + +$ git pull +# 若当前分支有追踪某个上游分支(/),则相当于下面两个命令 +# git fetch +# git merge / +$ git pull --rebase +# 同上,但使用 rebase 而不是 merge +``` + +## push + +Update remote refs along with associated objects + +```sh +$ git push +# Updates remote refs using local refs, while sending objects necessary to complete the given refs. + +git push --all origin +# 推送所有分支 + +$ git push --force-with-lease +# 强制推送,但比 --force 更安全:如果推送时,有其他人推送了新的提交,则此次推送会被拒绝 + +$ git push +# Works like git push , where is the current branch’s remote (or +# origin, if no remote is configured for the current branch). + +$ git push origin +# 若当前分支名和当前分支追踪的上游分支名相同,则该命令可作为 git push origin 的简写 + +$ git push origin : +# 将本地 src 分支推送到云端 dst 分支 + +$ git push origin -d +$ git push origin : +# 删除远程仓库中的 分支 + +$ git push -u origin +# 将本地分支 推送到云端,同时建立追踪关系。 + +$ git push --tags +# 上传本地所有标签。 + +$ git push origin : +$ git push origin :refs/tags/ +# 删除远程仓库中的 标签 +``` + +## remote + +Manage set of tracked repositories + +Manage the set of repositories ("remotes") whose branches you track. + +```sh +$ git remote +# shows a list of existing remotes. +# 通常只显示远程仓库的别名 origin + +$ git remote -v +# show remote url after name + +$ git remote add +# Add a remote named for the repository at + +$ git remote rename +# Rename the remote named to . + +$ git remote rm +# Remove the remote named . All remote-tracking branches and configuration settings for the remote are removed. + +$ git remote prune +# 剔除 中的无用分支或标签 + +git remote set-head origin -a +# 这个命令会自动设置远程仓库 origin 的默认分支。-a(或 --auto)选项告诉 Git 自动检测远程仓库的默认分支。 +# 当我在 Github 上重命名默认分支名时,Github 就会给出下面步骤: +# git branch -m main old-main +# git fetch origin +# git branch -u origin/old-main old-main +# git remote set-head origin -a +``` diff --git a/CLI/node/migrate.md b/CLI/node/migrate.md new file mode 100644 index 0000000..d1ce548 --- /dev/null +++ b/CLI/node/migrate.md @@ -0,0 +1,140 @@ +# 迁移中 + +## 基础 + +```sh +npm help +# 获取指定命令的帮助信息 + +-g | --global +# 全局 +-S | --save +# (默认)添加到 dependencies +-D | --save-dev +# 添加到 devDependencies + +$ npm ci +# 严格按照 package-lock.json 中指定的版本安装依赖 + +$ npm init [-y] +# 初始化 + +$ npm (ls | list) [--global] [--depth ] +# 查看安装的包。默认深度为 0。如果输出项中有 extraneous 标识,则标识该包不在 package.json 中。 +$ npm prune +# 从 node_modules 中移除 extraneous 标识的包。 + +$ npm login +# 登录 npm 账号 + +$ npm publish --access public +# 发布公共包 + +$ npm unpublish @ +# 取消某个包的发布,比如 npm unpublish @linhieng/camelcase@0.0.3 +``` + +## npx + +npx 可以用来运行本地命令。所谓本地命令,指的是 `.\node_modules\.bin\` 目录中的命令。在此之前,如果想要直接运行本地命令,需要在 package.json 中定义命令,然后通过 `npm run ` 的方式调用。 + +```sh +npx --no +# 包不存在时不要自动安装(--no-install 已废弃) + +npx --ignore-existing +# 不要使用本地缓存的模块,而是要强制下载 +# 可以通过 npm config get cache 查看环境路径 +``` + +## 配置 + +```sh +npm (c | config) set = [= ...] # 添加/修改配置 +npm (c | config) get [ [ ...]] # 查看配置 +npm (c | config) delete [ ...] # 删除配置 +npm (c | config) (list | ls) [--json] [-l | -long] # 查看配置列表 +npm (c | config) edit # 直接编辑配置文件 +npm (c | config) fix # 尝试修复无效的配置项 + +$ npm config list +# 输出自定义的配置项信息 + +$ npm config list -long +# 查看所有有效值(默认配置、自定义的配置)。 + +# 初始化项目时的 key 有: +init-author-name +init-author-email +init-author-url +init-license +init-version + +init-module +# 一个路径值,默认是 `~/.npm-init.js` + + +# 配置 registry +npm c set registry https://registry.npmmirror.com +# 这是新的淘宝镜像 +# 默认 https://registry.npmjs.org +# 腾讯 https://mirrors.cloud.tencent.com/npm/ +# 华为 https://mirrors.huaweicloud.com/repository/npm/ + +npm config set proxy http://127.0.0.1:7890 +# 配置代理 + +npm c get prefix +# 全局安装时的安装路径(node_global) + +npm c get cache +# 全局缓存的路径(node_cache) +``` + +## 全局模块 + +```sh +npm root +# Print the effective node_modules folder to standard out. + +npm root -g +# 获取全局 node_modules 目录 +``` + +[node 加载模块逻辑](https://nodejs.org/api/modules.html#loading-from-node_modules-folders) + +快速配置全局模块: +```powershell +New-Item -ItemType SymbolicLink -Target (npm root -g) -Path "$HOME\.node_modules" -Force +# 1. 在用户目录下创建 node_modules 符号链接 + +[Environment]::SetEnvironmentVariable("NODE_PATH", $(npm root -g), "Machine") +# 2. 添加 NODE_PATH 环境变量 +# 需要管理员权限 +``` + +## 安装本地模块 + +```sh +npm link +# 将当前目录下的模块作为作为全局模块。案例: +# https://github.com/nodejs/examples/blob/main/cli/commander/fake-names-generator/README.md + +npm link +# 安装本地某个模块,需要该模块已经 link 到全局模块中才可以安装。 + +npm unlink -g +# 卸载某个模块 + + +npm install -g +# 直接当前工作区正在开发的 npm 包安装到本地全局 +``` + +## package.json 中的 script + +`scripts` 字段中的 `key` 是[生命周期事件](https://docs.npmjs.com/cli/v10/using-npm/scripts#life-cycle-operation-order),`value` 是在该声明周期运行的命令。所以某些 key,比如 `install`, `prepare` 都是特殊的生命周期节点 + +在 package.json 中的 script 上设置环境变量时的语法取决于运行该脚本的终端。比如 bash 中的可以直接使用 `NODE_ENV=production` 设置环境变量。实际上,更推荐在 .env 文件中设置环境变量。 + +当运行 script 中的脚本,需要传递一个选项给脚本中的命令时,需要添加 `--`。比如 `npm run build -- --watch`,其中的 `--watch` 是传递给`build` 脚本中的命令,而不是传递给 npm 的选项。总的来说,`--` 用于标识后面的内容是参数,而不是选项。 diff --git a/CLI/shell/README.md b/CLI/shell/README.md new file mode 100644 index 0000000..14b770c --- /dev/null +++ b/CLI/shell/README.md @@ -0,0 +1,260 @@ +# shell + + +- 迁移说明 + - prompt 相关内容迁移至[博客文章](https://github.com/Linhieng/linhieng.github.io/blob/main/_posts/2024-02-22-style-ternimal.md),以后处理终端命令行提示符的内容都写在博客中 + - vim 的使用也迁移至博客文章中 + +对于来说,学习 linux,其实就是在学习命令,所以笔记的核心在于积累命令。 + +当看到 [Linux命令大全搜索工具](https://github.com/jaywcjlove/linux-command) 这个网站后,真的已经失去了继续在这里编辑 shell 笔记的想法,我也想要创建一个自己的命令工具网站! + +- [bash 键盘快捷键](#hotkey) +- [基础 shell 命令](reference/basic-shell.md) + - 简单命令 + - [echo](reference/basic-shell.md#echo) + - [date](reference/basic-shell.md#date) + - [whoami](reference/basic-shell.md#whoami) + - [pwd](reference/basic-shell.md#pwd) + - [lotout](reference/basic-shell.md#lotout) + - [exit](reference/basic-shell.md#exit) + - [clear](reference/basic-shell.md#clear) + - [tree](reference/basic-shell.md#tree) + - 帮助 + - [help](reference/basic-shell.md#help) + - [man](reference/basic-shell.md#man) + - [whatis](reference/basic-shell.md#whatis) + - 文件和目录 + - [cd](reference/basic-shell.md#cd) + - [ls](reference/basic-shell.md#ls) + - [file](reference/basic-shell.md#file) + - [find](reference/basic-shell.md#find) + - [stat](reference/basic-shell.md#stat) + - 文件权限 + - [chmod](reference/basic-shell.md#chmod) + - [chown](reference/basic-shell.md#chown) + - [chgrp](reference/basic-shell.md#chgrp) + - 文件和目录的创建、删除、修改 + - [cp](reference/basic-shell.md#cp) + - [mv](reference/basic-shell.md#mv) + - [rename](reference/basic-shell.md#rename) + - [touch](reference/basic-shell.md#touch) + - [mkdir](reference/basic-shell.md#mkdir) + - [rm](reference/basic-shell.md#rm) + - 查看内容 + - [cat](reference/basic-shell.md#cat) + - [less](reference/basic-shell.md#less) + - [more](reference/basic-shell.md#more) + - [head](reference/basic-shell.md#head) + - [tail](reference/basic-shell.md#tail) + - 字符串处理函数 + - [wc](reference/basic-shell.md#wc) + - [grep](reference/basic-shell.md#grep) + - [sed](reference/basic-shell.md#sed) + - [awk](reference/basic-shell.md#awk) + - [cut](reference/basic-shell.md#cut) + - [tr](reference/basic-shell.md#tr) + - 其他 + - [history](reference/basic-shell.md#history) + - [alias](reference/basic-shell.md#alias-unalias) + - [unalias](reference/basic-shell.md#alias-unalias) + - [diff](reference/basic-shell.md#diff) + - [cmp](reference/basic-shell.md#cmp) +- 文本处理(编辑、查看) + - [vim](reference/vim.md) +- 未好好整理 + - [systemctl](reference/other-shell.md#systemctl) + - [firewall-cmd](reference/other-shell.md#firewall-cmd) + - [hostname](reference/other-shell.md#hostname) + - [tar](reference/other-shell.md#tar) + - [wget](reference/other-shell.md#wget) + - [ps](reference/other-shell.md#ps) + - [top](reference/other-shell.md#top) + - [pidof](reference/other-shell.md#pidof) + - [kill](reference/other-shell.md#kill) + - [killall](reference/other-shell.md#killall) + - [ipconfig](reference/other-shell.md#ipconfig) + - [uanme](reference/other-shell.md#uanme) + - [uptime](reference/other-shell.md#uptime) + - [free](reference/other-shell.md#free) + - [who](reference/other-shell.md#who) + - [last](reference/other-shell.md#last) + - [df](reference/other-shell.md#df) + - [du](reference/other-shell.md#du) + - [fdisk](reference/other-shell.md#fdisk) + - [tee](reference/other-shell.md#tee) + +## hotkey + +Bash keyboard shortcuts + +| hotkey | | +| -------- | --- | +| `Ctrl+u` | | 清除光标左侧的所有内容 +| `Ctrl+k` | | 清除光标右侧的所有内容 +| `Ctrl+w` | | 删除光标前的一个单词 +| `Ctrl+c` | | 取消当前的输入行;结束当前任务 +| `Ctrl+d` | | 如果当前行为空,则退出终端(退出登录) +| `Ctrl+z` | | 挂起任务,后续可通过 `jobs` 查看 +| `Ctrl+l` | | 清屏(但不清空内容) + +| hotkey | | +| ---------------- | --- | +| `Ctrl+Backspace` | | 某些无法通过删除键删除的内容,再按下 ctrl。 +| `Ctrl+insert` | | 复制 +| `Shift+insert` | | 粘贴 + + +Bash Navigation + +| Shortcut | Action | +| ------------------ | --------------------------------------------------------------------------------- | +| Ctrl + A | Move to the start of the command line | +| Ctrl + E | Move to the end of the command line | +| Ctrl + F | Move one character forward | +| Ctrl + B | Move one character backward | +| Ctrl + XX | Switch cursor position between start of the command line and the current position | +| Ctrl + ] + x | Moves the cursor forward to next occurrence of x | +| Alt + F / Esc + F | Moves the cursor one word forward | +| Alt + B / Esc + B | Moves the cursor one word backward | +| Alt + Ctrl + ] + x | Moves cursor to the previous occurrence of x | + +Bash Control/Process + +| Shortcut | Action | +| -------- | ----------------------------------------------------------------- | +| Ctrl + L | Similar to clear command, clears the terminal screen | +| Ctrl + S | Stops command output to the screen | +| Ctrl + Z | Suspends current command execution and moves it to the background | +| Ctrl + Q | Resumes suspended command | +| Ctrl + C | Sends [SIGI](SIGI) signal and kills currently executing command | +| Ctrl + D | Closes the current terminal | + +Bash History + +| Shortcut | Action | +| --------------------- | -------------------------------------------------------------------- | +| Ctrl + R | Incremental reverse search of bash history | +| Alt + P | Non-incremental reverse search of bash history | +| Ctrl + J | Ends history search at current command | +| Ctrl + _ | Undo previous command | +| Ctrl + P / Up arrow | Moves to previous command | +| Ctrl + N / Down arrow | Moves to next command | +| Ctrl + S | Gets the next most recent command | +| Ctrl + O | Runs and re-enters the command found via Ctrl + S and Ctrl + R | +| Ctrl + G | Exits history search mode | +| !! | Runs last command | +| !* | Runs previous command except its first word | +| !*:p | Displays what !* substitutes | +| !x | Runs recent command in the bash history that begins with x | +| !x:p | Displays the x command and adds it as the recent command in history | +| !$ | Same as OPTION+., brings forth last argument of the previous command | +| !^ | Substitutes first argument of last command in the current command | +| !$:p | Displays the word that !$ substitutes | +| ^123^abc | Replaces 123 with abc | +| !n:m | Repeats argument within a range (i.e, m 2-3) | +| !fi | Repeats latest command in history that begins with fi | +| !n | Run nth command from the bash history | +| !n:p | Prints the command !n executes | +| !n:$ | Repeat arguments from the last command (i.e, from argument n to $) | + +Bash Editing + +| Shortcut | Action | +| --------------- | -------------------------------------------------------- | +| Ctrl + U | Deletes before the cursor until the start of the command | +| Ctrl + K | Deletes after the cursor until the end of the command | +| Ctrl + W | Removes the command/argument before the cursor | +| Ctrl + D | Removes the character under the cursor | +| Ctrl + H | Removes character before the cursor | +| Alt + D | Removes from the character until the end of the word | +| Alt + Backspace | Removes from the character until the start of the word | +| Alt + . / Esc+. | Uses last argument of previous command | +| Alt + < | Moves to the first line of the bash history | +| Alt + > | Moves to the last line of the bash history | +| Esc + T | Switch between last two words before cursor | +| Alt + T | Switches current word with the previous | + +Bash Information + +| Shortcut | Action | +| -------- | ------------------------------------------------------ | +| TAB | Autocompletes the command or file/directory name | +| ~TAB TAB | List all Linux users | +| Ctrl + I | Completes the command like TAB | +| Alt + ? | Display files/folders in the current path for help | +| Alt + * | Display files/folders in the current path as parameter | + + + +## 优质参考资料 + +- [Linux Journey](https://linuxjourney.com/) +- [Linux命令大全搜索工具](https://github.com/jaywcjlove/linux-command) + +[SIGI]: https://www.computerhope.com/unix/signals.htm#:~:text=The%20INT%20signal%20is%20sent%20to%20a%20process%20by%20its%20controlling%20terminal%20when%20a%20user%20wants%20to%20interrupt%20the%20process. diff --git a/CLI/shell/draft.md b/CLI/shell/draft.md new file mode 100644 index 0000000..bbb7792 --- /dev/null +++ b/CLI/shell/draft.md @@ -0,0 +1,169 @@ +# 草稿 + +## 算啥? + +```sh +{ echo hello; echo good; } | tr '[:lower:]' '[:upper:]' +# 分别将两个命令传递给 tr 执行 + +{ echo hello; echo good; } | wc +# 本以为会分别输出两行,结果确实两行相加 +``` + +## 文件系统 + +某些文件和文件夹的权限问题,不在此提及,因为这些都是可以改变的。有需要可以自己通过 `ll` 命令查看。 + +- /bin -> /usr/bin +- /usr + - /usr/bin 系统必备相关命令 + - /usr/sbin 系统管理相关命令 + - /usr/share 共享数据文件,如帮助文档、文档样式表、字体文件等 + - /usr/include 存放 C/C++ 等编程语言的头文件,这些头文件通常被开发者用来包含到自己的代码中,以便访问系统提供的库函数和数据结构。 + - /usr/local 存放用户自行安装的软件和程序,这些软件通常不是系统自带的。 + - /usr/src +- /etc 系统配置文件 +- /home 用户目录 +- /root 管理员的目录 +- /data 用来存储各种数据,如日志文件、数据库文件、应用程序数据等。 +- /boot 存储启动时所需的文件。这些文件包括操作系统的内核文件、引导加载程序(bootloader)的配置文件以及其他引导相关的文件。 +- /sbin -> /usr/sbin +- /lib -> /usr/lib +- /dev 设备驱动 (device) +- /opt 可选的(optional)应用程序或软件包的目录,通常由第三方软件包或大型应用程序使用。用于存放不属于操作系统发行版的软件包 +- /tmp 临时文件,系统重启后会清空 +- /var 存放频繁变化的文件、数据以及日志信息。 +- /misc 杂项 +- /proc 存放正则运行的各个进程。`ls /proc -v` 和 `ps -eo pid | ls -v` 的输出结果几乎相同。是一个虚拟文件系统(Virtual File System),用于提供关于当前运行中的进程和系统内核状态的信息。/proc 目录下的文件和子目录都是内核运行时动态生成的,因此它不是一个实际的文件系统,而是一个动态的信息源。 +- /sys 类似于 /proc 目录,它提供了与系统内核和硬件相关的信息。/sys 目录下的文件和子目录主要用于与系统的设备、驱动程序和内核参数进行交互,以及获取有关系统硬件和设备的详细信息。 +- /media 用于挂载可移动介质(如USB闪存驱动器、光盘、移动硬盘等)的标准目录。当插入可移动介质时,通常会自动将其挂载到 /media 目录下,以便用户可以访问其内容。 + +进程目录 /proc 下各目录详解 + +| | | +| ------- | --- | +| | | +| cmdline | | 启动当前进程的完整命令,但僵尸进程目录中的此文件不包含任何信息 +| cwd | | 指向当前进程运行目录的一个符号链接 +| environ | | 当前进程的环境变量列表,彼此间用空字符(NULL)隔开;变量用大写字母表示,其值用小写字母表示 +| exe | | 指向启动当前进程的可执行文件(完整路径)的符号链接,通过/proc/N/exe可以启动当前进程的一个拷贝 +| fd | | 这是个目录,包含当前进程打开的每一个文件的文件描述符(file descriptor),这些文件描述符是指向实际文件的一个符号链接 +| limits | | 当前进程所使用的每一个受限资源的软限制、硬限制和管理单元;此文件仅可由实际启动当前进程的UID用户读取(2.6.24以后的内核版本支持此功能) +| maps | | 当前进程关联到的每个可执行文件和库文件在内存中的映射区域及其访问权限所组成的列表 +| mem | | 当前进程所占用的内存空间,由open、read和lseek等系统调用使用,不能被用户读取 +| root | | 指向当前进程运行根目录的符号链接;在Unix和Linux系统上,通常采用chroot命令使每个进程运行于独立的根目录 +| stat | | 当前进程的状态信息,包含一系统格式化后的数据列,可读性差,通常由ps命令使用 +| statm | | 当前进程占用内存的状态信息,通常以“页面”(page)表示 +| status | | 与stat所提供信息类似,但可读性较好,每行表示一个属性信息 +| task | | 目录文件,包含由当前进程所运行的每一个线程的相关信息,每个线程的相关信息文件均保存在一个由线程号(tid)命名的目录中,这类似于其内容类似于每个进程目录中的内容;(内核2.6版本以后支持此功能) + + + +# 等待整理的内容 + +[旧笔记](https://juejin.cn/post/7222996508319678521) + +变量 + +| 类型 | 作用域 | 声明方式 | +| ---------- | --------------------- | --------------------------------------- | +| 自定义变量 | 当前 shell | declare <变量名>=<值> | +| 局部变量 | 当前 shell 及子 shell | export <变量名> 或 declare -x <变量名> | +| 全局变量 | 所有 shell | 在特定文件中的变量 | + + + +declare 语法: declare <选项> <变量名> + +| 选项 | 语义 | +| ---- | ------------------------------------------------------------ | +| -a | 将变量声明为数组类型 | +| -i | 将变量声明为整数型。 或者通过 let <变量名> 也能实现一样的效果 | +| -x | 将变量声明为环境变量 | +| -r | 将变量声明为只读变量 | +| -p | 输出变量及其被声明的类型 | + + + +常见特殊的变量 + +| 变量名 | 含义 | +| ------ | ----------------------------- | +| $0 | 当前 shell 名称 | +| $1 | 第1个参数,同理还有2,3,4… | +| $# | 传入的参数数量 | +| $* | 传入脚本的所有参数 | +| $? | 上条命令执行状态码。0表示正常 | +| $PS1 | 命令提示符 | +| $USER | 用户名 | +| $HOME | 用户主文件夹 | +| $PATH | 全局命令搜索路径 | + + +``` + + 系统全局变量的特定文件 + /etc/profile + ~/.bash_profile + ~/.bashrc + 用户登录进系统时,会依次执行(即 source 命令)上面三个文件,后执行的优先级高。所以,当我们修改文件时,我们也需要执行一下 source 命令才能让我们的更改生效。 + + +特殊字符 + 引号 + 双引号,部分引用,$变量名 或 ${变量名} 会被认为是变量 + 单引号,完全引用,原样输出 + 反引号,内容会被认为是可执行的命令 + 后台运行符号 & + 常搭配 nohup 命令,该命令表示不将内容输出到终端。搭配 & 可以实现后台运行某个程序。 + 命令连接符 + cmd1 || cmd2 。若 cmd1 运行失败(返回码非0),则运行 cmd2 + cmd1 && cmd2。若 cmd1 运行成功(返回码0),则运行 cmd2 + cmd1 ; cmd2 。两条命令互不干扰 + 管道 | +cmd1 | cmd2 。能将 cmd1 的输出作为 cmd2 的输入。 + +``` + +重定向符号 + +| 符号 | 说明 | +| ------------- | ------------------------------------ | +| > | 覆盖写入文件 | +| >> | 追加写入文件 | +| 2> | 将错误输出写入文件 | +| &> | 正确和错误输出统一写入到文件中 | +| < | 重定向输入,后可接文件 | +| << <结束字符> | 多行文件输入,后面带一个结束输入字符 | + + +``` + 判断 + 有三种判断方式: + § test <条件>,案例如下 + test "$a" -eq "$b" ; echo $? + § [ <条件> ],案例如下 + [ "$a" -gt "$b" ] ; echo $? + § [[ <条件> ]],支持正则,案例如下: + [ "$a" -lt "$b" ] ; echo $? + [[ "$a" =~ [0-9]+ ]] ; echo $? + 或者使用正则变量,注意正则变量不要带双引号 + pattern="[0-9]+" + [[ "$a" =~ $pattern ]] ; echo $? + 注意中括号内部要有空格。 + 注意使用变量时要带双引号,不然变量中的空格会影响结果。 + + +语句 + 条件语句 + if [[ $a > $b ]] ; then + echo "$a > $b" + elif [[ $a < $b ]] ; then + echo "$a < $b" + else + echo "$a == $b" + fi + 因为我们将 then 写在后面,所以需要将分号表示是两个语句。或者不加分号,然后 then 单独成行 + +还有一些内容没转移: +``` diff --git a/CLI/shell/journey.md b/CLI/shell/journey.md new file mode 100644 index 0000000..9874582 --- /dev/null +++ b/CLI/shell/journey.md @@ -0,0 +1,345 @@ +## 用户权限 + +sudo 全称 superuser do + +su 全称 substitute users + +组代表的仅仅只是一系列用户的集合。所以当你创建一个新用户时,你会发现 `/etc/group` 中也会多出一个组。 + +```sh +sudo cat /etc/sudoers +# 并不是每个用户都有 sudo 权限的 +# 不同用户的 sudo 权限所拥有的权限也是不同的 +# 这就是这个文件的作用,但通常是在 /etc/sudoers.d 文件中编辑,而不是直接在 /etc/sudoers 中编辑 + +sudo cat /etc/passwd | column -t -s ':' +# root:x:0:0:root:/root:/bin/bash + +sudo cat /etc/shadow | column -t -s ':' +# root:*:19752:0:99999:7::: + +sudo cat /etc/group | column -t -s ':' +# root:x:0: + +sudo useradd alan +# 添加用户。或者使用 adduser 也是一样的 +sudo userdel alan +# 删除用户 +sudo passwd alan +# 设置/修改特定用户密码 + +# 同理,还有 +groupadd +groupdel +usermod -aG +# 添加 user 到 group 中 + +passwd +# 设置/修改当前用户密码 +``` + +`/etc/passwd` 以 colon 分割,从左到右依次是: + - Username + - User's password. + - ` ` 空白表示没有设置密码。 + - `x` 表示密码存储在 /etc/shadow file + - `*` 表示该用户没有登录权限 + - User ID (UID) + - Group ID (GID) + - GECOS field 备注信息,比如联系方式。使用逗号分割。 + - User's home directory + - User's shell. 通常都是 bash + + +`/etc/shadow` 以 colon 分割,从左到右依次是: + - Username + - Encrypted password 加密后的密码,* + - Date of last password changed 为 0 表示用户登录时需要修改密码。 + - Minimum password age 表示密码最短寿命,至少超过这个时间后才可以再次修改密码 + - Maximum password age 表示密码的最长寿命,超过这个时间后必须修改密码。 + - Password warning period 表示密码即将过期前多少天给出警告信息 + - Password inactivity period 表示密码过期后,还允许登录的时间期限 + - Account expiration date 表示超过这个日期后用户将无法登录 + - Reserved field for future use 保留字段 + +`/etc/group` 以 colon 分割,从左到右依次是: + - Group name + - Group password 组并不需要密码,默认设置为 * + - Group ID (GID) + - List of users + +```sh +cat /etc/passwd | column -t -s ':' +# 输出 passwd 并对齐 + +{ echo "Username:Password:UID:GID:User Description:Home Directory:Login Shell"; cat /etc/passwd; } | column -t -s ':' +# 添加表头 + +{ echo "Name:Password:ID:Members"; cat /etc/group; } | column -t -s ':' +# 输出组 + +``` + +## 权限 + +`d | rwx | r-x | r-x ` 文件权限格式说明,从左到右依次是: + +- filetype 文件类型, + - `d` 表示目录。 + - `-` 表示一般文件 +- user permissions 用户权限 +- group permissions 组权限 +- other permissions 其他用户/组权限 + +rwx- 分别表示可读、可写、可执行文件、无权限/非可执行文件。 + +```sh +$ ls -l +# 显示详细信息,第一个字段就是文件权限信息 + +$ chmod ugo+rwx +$ chmod ugo-rwx +$ chmod u+r +$ chmod g+r +# u 表示用户 +# g 表示组 +# o 表示其他 +# + 表示添加权限 +# - 表示移除权限 + +# 4 (100) r +# 2 (010) w +# 1 (001) x +# 0 (000) - +# 100 100 100 表示 r-- | r-- | r-- +# 010 010 010 表示 -w- | -w- | -w- +# 001 001 001 表示 --x | --x | --x +# 101 101 101 表示 r-x | r-x | r-x +# 111 111 111 表示 rwx | rwx | rwx +# ... +# 所以 +$ chmod 777 # chmod ugo+rwx +$ chmod 000 # chmod ugo-rwx +$ chmod 444 +$ chmod 222 +$ chmod 111 +$ chmod 755 +$ chmod 700 +``` + +修改所有权(Ownership Permissions) + +```sh +$ sudo chown user +# Modify user ownership + +$ sudo chgrp group +# Modify group ownership + +$ sudo chown user:group +# Modify both user and group ownership at the same time + + +umask 022 +umask 777 # 取走所有权限 +umask 000 # 不取走权限,x 不是权限,所以默认是 - +# 配置创建文件/文件夹时的默认权限 +# 数值表示拿走哪些权限,比如 022 表示不取走 user 的权限, +# 但对于 group 和 other,要取走他们的 w 权限。 +``` + +### s + +Setuid 是一种文件系统属性,它允许一个程序在执行时以文件所有者的权限来运行,而不是以执行程序的用户的权限来运行。Setuid 是 Set User ID 的缩写,意味着它会在执行时设置程序的有效用户 ID 为文件所有者的用户 ID。 + +通常情况下,当用户执行一个程序时,该程序以当前用户的权限来执行。但是,当设置了 Setuid 属性的程序被执行时,它会以文件所有者的权限来执行,即使执行程序的用户不是文件的所有者。 + +```sh +# 当我们使用 passwd 命令时,它实际上是会修改 /etc/passwd 的 +# 但这个文件不应该是管理员所有吗?为什么我们能修改呢? + +$ ls -l /etc/passwd +-rw-r--r-- 1 root root 965 Feb 6 19:36 /etc/passwd + +# 原因在于 passwd 命令 +$ ls -l /usr/bin/passwd +-rwsr-xr-x 1 root root 63960 Feb 7 2020 /usr/bin/passwd + +# 可以看到文件权限中多了个 s 符号,这表示在执行 passwd 命令时, +# 将已文件所有者的权限执行。该文件的所有者是 root,所以我们可以修改 /etc/passwd 文件 + +chmod u+s +chmod 4755 +# 4 表示 setuid +``` + +### set group id + +类似的,还有 setgid (set group ID, SGID),它允许用户已文件所属组的权限运行。 + +```sh +ls -l /usr/bin/wall +-rwxr-sr-x 1 root tty 35048 Jan 20 2022 /usr/bin/wall + +chmod g+s +chmod 2555 +# 2 表示 set group id +``` + +### t + +此外还有 t 标识,它表示 The Sticky Bit,意为只有文件所有者,或者 root 用户才能删除或修改文件。 + +```sh +$ ls -ld /tmp +drwxrwxrwt 1 root root 12288 Mar 17 04:46 /tmp + +# 可以看到 /tmp 文件夹,任何用户都可以添加文件、写入文件,但不能删除文件(除了 root 用户) + +$ chmod +t + +$ chmod 1755 +# 1 表示 t +``` + +### 进程权限 + +注意,能给运行带有 s 标志的 root 用户的进程,并不意味你临时拥有了 root 权限。 + +linux 中有很多 uid,其中和进程权限相关的,有三种: + +- effective user ID 有效用户 id。它授予了进程执行时的权限。比如 alan 用户运行 passwd 命令(程序)时,它的有效用户 id 就是 alan。噢,等等,由于 passwd 设置了 s 标志,所以它的有效用户 id 应该是 passwd 文件的拥有者,也就是 root 用户。 + +- real user ID 真实用户 id。顾名思义,谁运行的程序,真实用户 id 就是谁。 + +- saved user ID 保存的用户 id。它进程根据需要在有效用户标识和真实用户标识之间进行切换。这种灵活性很重要,因为始终以提升的权限运行不是安全的做法。 + +所以,当你修改另一个用户时,进程知道你的真实 uid,也知道你要修改用户 uid,所以你没有权限修改(除非 root) + +## package + +包主要分为两类: + +- debian (.deb) 系列,主要用于 Debian, Ubuntu, LinuxMint。 + - dpkg 基础 + - apt 增强 +- Red Hat (.rmp) 系列,主要用于 Red Hat Enterprise Linux, Fedora, CentOS, etc + - rpm 基础 + - yum 增强 + + +### gzip 和 tar + +`gzip` 是一个用于压缩文件的程序,压缩后的文件名为 `.gz`。但它无法压缩多个文件 + +```sh +$ gzip +# 压缩 +$ gunzip +# 解压缩 +``` + +`tar` 命令则可以让我们将多个文件打包成一个文件,后缀名为 `.tar`。 + +```sh +$ tar cvf +# 打包 +``` + +- c 表示 create +- v 表示 verbose,输出操作日志 +- f 表示 filename,后面跟打包后的文件名 + +```sh +$ tar xvf +# 解包 +``` + +- x 表示 extract 也就是解包 +- v 表示 verbose 输出操作日志 +- f 表示 filename,后面跟要解包的文件名 + + +综上,linux 中的 package 借助 tar 来将多个文件捆绑成一个文件,然后用 gzip 对其压缩,所以很多 linux 文件的后缀名都是 .tar.gz。通常,可以借助 tar 命令的 `z` 参数整合 gzip 命令 + +```sh +$ tar czf +# 打包并压缩 + +$ tar xzf -C +# 解压,并解包到特定文件夹中。文件夹 folder 需存在 +``` + +### 包管理的基础命令:rpm 和 dpkg + +```sh +# 安装 +dpkg -i some_deb_package.deb +rpm -i some_rpm_package.rpm +# -i 表示 --install + +# 卸载 +dpkg -r some_deb_package.deb +rpm -e some_rpm_package.rpm +# r 表示 remove +# e 表示 erase + +# 查看已安装列表 +dpkg -l +rpm -qa +# -l 表示 list +# q 表示 query;a 表示 all +``` + +### 包管理中的蝙蝠侠,rpm 和 dpkg 的增强板工具:yum 和 apt + +```sh +$ apt install package_name +$ yum install package_name +# 安装 + +$ apt remove package_name +$ yum erase package_name +# 卸载 + +apt update ; apt upgrade +yum update +# 更新 + +apt show package_name +yum info package_name +# 获取已安装的 package 信息 +``` + +### 编译源码 + + + +```sh +sudo apt install build-essential +# 首先,需要一个工具来帮助我们编译源码 + +tar -xzvf package.tar.gz +# 解压源码 + +./configure +# 一般来说,源代码中会有相关编译说明,而且会提供一个 configure 脚本文件 +# 用于检查编译所需要的相关依赖等功能。 + +make +# 源代码中会有一个 Makefile 文件,指定了构建软件时所需要的规则 +# 当运行 make 命令时,就会查看这个 Makefile 文件 + +sudo make install +# 安装软件。这个命令会将文件拷贝到电脑中的特定位置上 + +sudo make uninstall +# 卸载软件 + +# 注意,使用 make 在后台安装软件时,你可能并不知道它到底处理了哪些文件 +# 当你删除文件时,你并不知道应该有哪些文件被删除。 +# 所以,推荐使用下面命令,而不是 sudo make install +sudo checkinstall +# 这个命令本质上也是在运行 make install 命令 +# 然后将其构建成一个 .deb 文件,这样能给更方便的安装和卸载 +``` diff --git a/CLI/shell/nginx.md b/CLI/shell/nginx.md new file mode 100644 index 0000000..3d10261 --- /dev/null +++ b/CLI/shell/nginx.md @@ -0,0 +1,177 @@ +# nginx + +```sh +whereis nginx +# 查找 nginx 命令路径 + +sudo nginx +# 启动 + +sudo nginx -s stop +# 立即停止 + +sudo nginx -s quit +# 处理完当前连接后再停止 + +sudo nginx -s reload +# 重新加载配置文件。 + +sudo nginx -t +# 测试当前配置文件是否有效。会输出配置文件路径 +``` + +## nginx 问题 + +修改路径后发现 403 错误 + 403 错误有一个原因就是权限不够 + 如果网页文件是放在 root 路径下,那么就会报 403 + +安装了 nginx 后还是无法打开网页 + 1. 安装后是否有输入 nginx 运行一下呢? + 2. 云端服务器的安全组中是否有开启 80 端口? + +修改配置文件或 root 路径后发现网页不无效 + 1. 没有注释掉 include /etc/nginx/conf.d/*.conf + (始终调用默认配置,怎么可能修改有效呢?) + 2. 修改后没有重启一下 nginx,重加载命令是 nginx -s reload + + +## nginx.conf 配置说明 + +`$uri` 变量是什么? + 访问:http:itaem.cn;39000/js/a.js + $uri 就是 /js/a.js + +## nginx.conf 配置示例 + +最简单的配置 + +```conf +http { + server { + listen 80; # 设置监听端口 + root /usr/80; # 会自动获取此目录下的 index.html 文件 + } +} +``` + +添加证书: + +```conf +server { + listen 443; + root /usr/443; + + # 开启 ssl + ssl on; + # 指定 ssl 证书路径 + ssl_certificate /path/to/example.com.crt; + # 指定私钥文件路径 + ssl_certificate_key /path/to/example.com.key; +} +``` + +添加请求头 + +```conf +server { + listen 39000; + location / { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` + +代理后台服务器 + + 浏览器想服务器的 39000 端口发送 get 请求后,nginx 会将该请求转发给服务器上的 3900 端口。 + +```conf +server { + listen 39000; + location / { + proxy_pass http://127.0.0.1:3900; + } +} +``` + +将 80 端口永久重定向到 443 端口 + +```conf + server { + listen 80; + # root /usr/80; + server_name oonoo.cn; + + location / { + return 301 https://$host$request_uri; + } + } +``` + +或 +```conf + server { + listen 80 default_server; + # server_name oonoo.cn; + listen [::]:80 default_server; + + location / { + return 301 https://$host$request_uri; + } + } +``` + +直接通过 IP 访问时,直接转到域名。(如果故意通过 ip 访问 443 端口,则无效) + +```js + + server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; # 匹配任何域名,包括空字符串 + + location / { + return 301 https://www.oonoo.cn$request_uri; + } + } + server { + listen 80; + listen [::]:80; + server_name oonoo.cn www.oonoo.cn; + location / { + return 301 https://$host$request_uri; + } + } + server { + listen 443; + root /usr/443; + + ssl on; + ssl_certificate /.keys/cert.pem; + ssl_certificate_key /.keys/key.key; + } +``` + +## 卸载 nginx + +// 使用的 yum 安装的 nginx + +1. 输入 `ps -ef | grep nginx` 检查是否还在运行 nginx + +2. 输入 `nginx -s stop` 停止运行 nginx + +3. 输入 `netstat -lntp` 查看一下 80 端口是否关闭 + +4. 输入 `whereis nginx` 查找 nginx 的相关文件路径 + +5. 输入 `find / -name nginx` 查找 nginx 的相关文件 + +6. 删除找到的相关文件 + `rm -rf /usr/sbin/nginx` + ... + 一个一个删(有没有快捷方法?) + +7. 再使用 yum 清理一下 + `yum remove nginx` diff --git a/CLI/shell/reference/basic-shell.md b/CLI/shell/reference/basic-shell.md new file mode 100644 index 0000000..1b0228c --- /dev/null +++ b/CLI/shell/reference/basic-shell.md @@ -0,0 +1,1303 @@ +# basic-shell + +## echo + +在终端输出字符串或变量提取后的值。 + +```sh +echo $ +# 输出变量值 +$ export name="Tom" +$ echo $name + + + +$ echo `pwd` +$ echo $(pwd) +# 输出命令运行结果 +``` + +## date + +支持格式 + +| var | | +| ---- | --- | +| `%a` | |当地时间的星期名缩写(例如: 日,代表星期日) +| `%A` | |当地时间的星期名全称 (例如:星期日) +| `%b` | |当地时间的月名缩写 (例如:一,代表一月) +| `%B` | |当地时间的月名全称 (例如:一月) +| `%c` | |当地时间的日期和时间 (例如:2005年3月3日 星期四 23:05:25) +| `%C` | |世纪;比如 %Y,通常为省略当前年份的后两位数字(例如:20) +| `%d` | |按月计的日期(例如:01) +| `%D` | |按月计的日期;等于%m/%d/%y +| `%F` | |完整日期格式,等价于 %Y-%m-%d +| `%j` | |按年计的日期(001-366) +| `%p` | |按年计的日期(001-366) +| `%r` | |当地时间下的 12 小时时钟时间 (例如:11:11:04 下午) +| `%R` | |24 小时时间的时和分,等价于 %H:%M +| `%s` | |自UTC 时间 1970-01-01 00:00:00 以来所经过的秒数 +| `%T` | |时间,等于%H:%M:%S +| `%U` | |一年中的第几周,以周日为每星期第一天(00-53) +| `%x` | |当地时间下的日期描述 (例如:12/31/99) +| `%X` | |当地时间下的时间描述 (例如:23:13:48) +| `%w` | |一星期中的第几日(0-6),0 代表周一 +| `%W` | |一年中的第几周,以周一为每星期第一天(00-53) + +```sh +date +# 输出当前日期 + +date "+%Y-%m-%d %H:%M:%S" +# 指定格式 + +date "+%j" +# 查看今天是当年中的第几天 + + +``` + +## whoami + +```sh +whoami +# 打印用户名。如 root、keety 等等 +``` + +## pwd + +```sh +pwd +# Print Working Directory +``` + +## clear + +```sh +clear +# 清屏。clear up your display +# 如果不想清屏,可以通过 Ctrl+L 快捷键 +``` + +## tree + + +```sh +tree +# 为当前目录生成目录树 + +tree +# 为特定目录生成目录树 + +tree -d +# 只显示目录 + +tee -L +# 指定深度 +``` + + +## lotout + +```sh +logout +# 登出 +``` + +## exit + +```sh +exit +# 退出,同 logout +``` + +## cd + +Change Directory + +```sh +cd +# 切换目录 + +cd . +# 当前目录。(current directory). This is the directory you are currently in. + +cd .. +# 父目录。(parent directory). Takes you to the directory above your current. + +cd ~ +# 主目录。(home directory). This directory defaults to your “home directory”. Such as /home/pete. + +cd - +# 前目录。(previous directory). This will take you to the previous directory you were just at. + +cd +# 不携带任何 flags/arguments/options 时,等同 cd ~ +``` + +## ls + +List Directories + +文件权限: + +```sh + +ls +# 查看当前目录下的文件。不显示 . 开头的文件 + +ls -a +# 显示以 . 开头的文件和文件夹 + +ll +ls -l +# 显示详细信息。shows a detailed list of files information: +# file permissions +# number of links +# owner name 属主 +# owner group 属组 +# file size +# timestamp of last modification +# file/directory name. + +ls -R +# recursively list directory contents + +ls -t +# sort by modification time, newest first + +ls -r +# 倒序。reverse order while sorting + + + +ll /proc/PID +# 在该进程下的文件夹中,就有进程运行所在文件夹的信息 +# cwd -> /usr/image-hosting-2 +# cwd 后面的就是程序运行所在文件夹 +# +# exe -> /usr/local/node/bin/node +# exe 后面代表的是执行程序的文件夹 + + + +ls -1 +# 以每行一个文件的方式进行列出 +ls -c +ls -C +ls -X +# 递增。先上到下,再左到右 +ls -x +# 递增。先左到右,再上到下 +ls -v +# 按数字大小排序。上到下,左到右 + +ls -1 target-folder2/ | sed "s|^|$(realpath target-folder2/)/|" | xargs -I {} mv {} target_folder/ +# 将 target-folder2/ 中的内容都移动到 target_folder 中。 +# 也可以用 mv target-folder2/* target_folder 代替。 +``` + +--- + +每个终端窗口,都会在 /dev/pts/ 目录下有对应的伪终端设备文件 +`ls /dev/pts` +如果只有一个窗口,则输出 0 ptmx +0 文件夹就是伪终端设备文件 + +将输出内容输出到对应的伪终端设备文件,则会将内容输出到对应的终端窗口 + +在第一个终端窗口(0)下执行命令: +`echo "hello" > /dev/pts/1` + +此时第二个终端窗口(1)会显示 hello + +--- + +## chmod + +用于修改文件权限 mode,-R 参数以递归方式对子目录和文件进行修改。 + +```sh +ls -l +# 可显示文件权限,含义: +# - 第 1 位表示存档类型。 +# - 表示一般文件 +# d 表示目录 +# l 表示链接文件 +# - 接着 3 位表示当前用户的权限(属主权限)。 +# rwx 依次表示读、写、执行权限。对应八进制表示为4、2、1。 +# 若某位上为 - 则表示无该权限 +# - 再接着 3 位同用户组的用户权限(属组权限) +# - 再接着 3 位不同用户组的用户权限(其他用户权限)。 +# - 第 11 位(如果有的话)是一个半角句号. 表示 SELinux 安全标签。 + +$ echo "echo hello world" > hello.sh +# 创建一个脚本文件 +$ ll | grep hello.sh +# 查看权限,此时是 -rw-r--r-- 没有执行权限。此时权限把八进制是 644 +$ chmod u+x hello.sh +# 为其增加属主的执行权限。 +# u表示属主,g表示属组,o表示其他,a表示所有用户。 +$ ll | grep hello.sh +# 查看权限,此时是 -rwxr--r-- 拥有主执行权限。此时权限把八进制是 744 +$ chmod u-x hello.sh +# 撤销属主的执行权限。 +$ chmod 744 hello.sh +# 为其增加属主的执行权限。 +$ /bin/bash hello.sh +# 使用 bash 命令解释器执行 hello.sh 脚本文件。 + + + +$ chown -R keety /path/to/abc +# 将 /path/to/abc 文件夹的所有者设置为 keety 用户 + +$ chmod -R u+rwx /path/to/abc +# 为 keety 分配 /path/to/abc 文件夹的 rwx 权限 +``` + +## chown + +chown 命令修改文件的属主和属组;-R参数以递归方式对子目录和文件进行修改;`ls -l` 命令显示的第三列和第四列就是文件的属主和属组信息。 + +```sh + +$ whoami +root +$ touch test.txt +$ ll | grep test.txt +-rw-r--r-- 1 root root 0 Feb 20 23:30 test.txt +$ adduser test +$ adduser admin +# 可以通过 passwd 设置密码 +$ chown test test.txt && ll | grep test.txt # 设置属主用户为 test +-rw-r--r-- 1 test root 0 Feb 20 23:30 test.txt +$ chown admin:admin test.txt && ll | grep test.txt # 设置属主和属组用户为 admin +-rw-r--r-- 1 admin admin 0 Feb 20 23:30 test.txt +``` + +## chgrp + +chgrp命令用于修改文件的属组。 + +```sh +$ ll | grep test.txt +-rw-r--r-- 1 admin admin 0 Feb 20 23:30 test.txt +$ chgrp root test.txt && ll | grep test.txt +-rw-r--r-- 1 admin root 0 Feb 20 23:30 test.txt +``` + + + +## touch + +```sh +touch +# 创建文件或更新已存在文件的 timestamps。Make some files, also also used to change timestamps on existing files and directories + +touch -- -file +# 创建一个名为 -file 的文件 +``` + + +## file + + +用于辨识文件类型 + +```sh +file +# 查看文件类型。Linux 中不会根据后缀名来判断文件类型。 +# 可能的输出有: +# directory +# ASCII text +# HTML document, ASCII text, with very long lines +# HTML document, UTF-8 Unicode text, with CRLF line terminators +# sbin: symbolic link to usr/sbin + +file -b +# 列出辨识结果时,不显示文件名称 + +file -c +# 详细显示指令执行过程,便于排错或分析程序执行的情形 + +file -L +# 直接显示符号连接所指向的文件类别,如 file /sbin -L 则直接显示 directory + +file -f +# 使用自定义 magic 文件,来解析文件类型 +# 未尝试过! +``` + + +## cat + +用于查看内容较少的纯文本文件 + +```sh +cat +# 输出文件内容。 + +cat /dev/null > +# 清空文件内容 + + +cat -n +cat --number +# 输出行号 + +cat -b +cat --number-nonblank +# 输出行号,但是不对空白行进行编号 + +cat -s +cat --squeeze-blank +# 当遇到有连续两行以上的空白行,只输出一行的空白行 + +cat -E +cat --show-ends +# display $ at end of each line + +cat -T +cat --show-tabs +# display TAB characters as ^I + +cat -ns +# 当遇到有连续两行以上的空白行,只输出一行的空白行。然后进行编号 + +cat -bs +# 当遇到有连续两行以上的空白行,只输出一行的空白行。然后对非空白行进行编号 + + +cat /etc/os-release +cat /proc/version +# 查看 linux 内核版本 + + +cat /etc/redhat-release +# 列出所有版本信息,只适合Redhat系的Linux +cat /etc/issue +# 列出所有版本信息,适用于所有的Linux发行版 +``` + +## less + +与 `more` 命令相似,但使用 `less` 可以随意浏览文件,而 `more` 仅能向前移动,却不能向后移动。 + +| hotkey | info | +| --------------------------- | ---- | +| `q` | | 退出 +| `y` | | 向前滚动一行 +| `Enter` | | 向后滚动一行 +| `u` | | 向前翻半页 +| `d` | | 向后翻半页 +| `PgUp` / `b` / `Ctrl+B` | | 上翻一页 +| `PgDn` / `Ctrl+F` / `Space` | | 下翻一页 +| `g` | | 跳到首页 +| `G` | | 跳到尾页 +| `h` | | 帮助 +| `/` | | 向下搜索字符串 +| `?` | | 向上搜索字符串 +| `n` | | 重复前一个搜索 +| `N` | | 反向重复前一个搜索 + +```sh +less +# 分页查看文件内容。 + +less -e +# 当文件显示结束后,自动离开 + +less -m +# 显示类似 more 命令的百分比 + +less -N +# 显示每行的行号 + +less -s +# 显示连续空行为一行 + +less +10 -Nm +# 从第 10 行开始,并且显示行号和百分比 +``` + +## more + +从前向后分页显示文件内容。 + +| hotkey | info | +| ------------------- | ---- | +| `Enter` | | 向下 n 行,n 需要定义,默认为 1 行 +| `Ctrl+F` / `Space` | | 向下滚动一页 +| `Ctrl+B` | | 向上滚动一页 +| `=` | | 输出当前行的行号 +| `!` | | 执行 `` +| `q` | | 退出 + +案例: + +```sh +more +20 /var/log/messages +# 从第 20 行开始分页查看系统日志文件 /var/log/messages。 +``` + +## history + +用于显示历史执行过的命令。 + +bash默认记录 1000 条执行过的历史命令,被记录在 `~/.bash_history` 文件中。 + + + +```sh +history +# 查看命令历史 +# Ctrl+R 可以搜索历史命令 + +history 10 +# 显示最新 10 条记录 + +history -c +# 清除历史记录。 +``` + + +## cp + +```sh +cp +# Copy. 将 src 复制到 dest 位置 +# 可以借助通配符(wildcards)拷贝多个文件 +# * represent all single characters or any string +# ? represent one character +# [] represent any character within the brackets + +cp -r +# 递归拷贝非空文件夹。recursively copy the files and directories within a directory + +cp -i +# 覆盖(overwritten)文件时提示。输入 y 则确定覆盖,直接回车则取消 + + + +-d +# 复制时保留链接 +-f +# 覆盖已经存在的目标文件而不给出提示 +-p +# 除复制文件的内容外,还把修改时间和访问权限也复制到新文件中 + +``` + +## mv + +```sh +mv +# Move. 移动并重命名。moving files and also renaming them + +mv +# 移到指定目录 + +mv -i +# prompt you before overwriting anything + +mv -b +# 覆盖时,重命名旧文件(在末尾添加 ~)。 + + +-f +# 如果目标文件已经存在,不会询问而直接覆盖 +``` + +## rename + +rename命令用字符串替换的方式批量改变文件名。rename命令有C语言和Perl语言两个版本,这里介绍C语言版本的rename命令,不支持正则表达式。 + +```sh +$ touch demo1.txt demo2.txt +$ ls | grep -i demo +demo1.txt +demo2.txt +$ rename demo DEMO * +$ ls | grep -i demo +DEMO1.txt +DEMO2.txt +# 将当前目录下所有文件名中的字符串demo改为大写的字符串DEMO。 + +$ ls | grep -E '\.(text|txt)' +DEMO1.txt +DEMO2.txt +$ rename .txt .text * +$ ls | grep -E '\.(text|txt)' +DEMO1.text +DEMO2.text +# 将当前目录下所有.txt文件后缀都改为text。 +``` + +## mkdir + +```sh +mkdir +# Make Directory. 创建文件夹 + + +$ mkdir test/{a,b,c,d} +# 创建多个文件夹, 主要分隔符是逗号, 且不能有空格 + +mkdir -p +# parent flag. 创建时同时创建子文件夹 + +mkdir -p .github/workflows && touch $_/pages.yml +# 创建 .github/workflows/pages.yml 文件 + +``` + +## rm + +```sh +rm +# Remove. 删除文件和空文件夹。 +# 注意!删除后文件并不会放入所谓的回收站(trash),而是直接消失了 + +rm -f +# 强制删除所有文件,包括受保护的文件。 + +rm -i +# give you a prompt on whether you want to actually remove the files or directories. + +rm -- -file +# 删除一个名为 -file 的文件 +``` + + +## find + +该命令用来在指定目录下查找文件。任何位于参数之前的字符串都将被视为欲查找的目录名。如果使用该命令时,不设置任何参数,则find命令将在当前目录下查找子目录与文件。并且将查找到的子目录和文件全部进行显示。 + +```sh +find [参数] [文件] +# -mount 只检查和指定目录在同一个文件系统下的文件,避免列出其它文件系统中的文件。 +# -amin n 在过去n分钟内被读取过文件。 +# -type c 文件类型是c的文件。 +# -cmin n 在过去n分钟内被修改过。 +# -name name 查找文件名称为name的文件。 + +find / -type f -size 0 -exec ls -l {} \; +# 查找系统中所有文件长度为0的普通文件,并列出它们的完整路径。 + +find . -maxdepth 2 -type d -name "node_modules" +# 查找 node_modules + +find . -maxdepth 2 \( -name "*.js" -o -name "*.html" -o -name "*.json" \) -exec dirname {} \; | uniq +# 查找当前目录中包含 js / html / json 文件的文件夹 + +find . -maxdepth 2 -type d -exec sh -c 'ls -1 "{}"/*.js >/dev/null 2>&1' \; -print +# 查找包含 js 文件的文件夹 + +find . -maxdepth 2 \( -name "*.js" -o -name "*.html" -o -name "*.json" \) -exec dirname {} \; | uniq | xargs -I {} mv {} target_folder +# 将找到的目录全部移动到 target_folder 目录中。 + +``` + + +```sh + +find -name +# 在当前目录递归查找名称为 的文件 + +find -name +# 在 目录递归查找名称为 的文件 + +find -type d -name +# 指定查找类型为目录 + + +find . -type d -name "node_modules" -prune -o -type f -name "playwright.config*" +# 在该目录下查找 playwright.config 开头文件,并忽略 node_modules 文件夹 +``` + +## help + +```sh +help +# 获取命令帮助。 +# 对于其他命令,通常还可以借助 --help 参数来获取帮助。比如 tree --help + +# 案例: +$ help echo +echo: echo [-neE] [arg ...] + Write arguments to the standard output. + + Display the ARGs, separated by a single space character and followed by a + newline, on the standard output. + + Options: + -n do not append a newline + -e enable interpretation of the following backslash escapes + -E explicitly suppress interpretation of backslash escapes + # .... +$ logout --help +logout: logout [n] + Exit a login shell. + + Exits a login shell with exit status N. Returns an error if not executed + in a login shell. +#------------- +``` + +## man + +```sh +man +# 获取命令手册——更详细的使用说明。 +``` + +## whatis + +如果发现该命令不生效,始终输出 "nothing appropriate",则需要先运行 `mandb`。 [参考来源](https://stackoverflow.com/questions/11774230/unix-cygwin-whatis-returns-all-commands-as-nothing-appropriate) + +```sh +whatis +# 获取命令的简短描述,内容来自手册(man) +``` + +## unalias + +```sh +unalias +# 移除别名 +``` + +## alias-unalias + +```sh +alias = +# 为某个长命令配置一个别名。 +# 如果想要长期生效,可以将该命令添加到 ~/.bashrc 文件中 + +alias +# 查看所有命令别名 + +alias get-all-user='{ echo "Username:Password:UID:GID:User Description:Home Directory:Login Shell"; cat /etc/passwd; } | column -t -s ":"' + +alias get-all-group='{ echo "Name:Password:ID:Members"; cat /etc/group; } | column -t -s ":"' +``` + +## head + +用于查看文件开头指定行数的内容。 + + +```sh +head +# 显示前 10 行内容 + +head -n +# 显示开头指定行数的文件内容 + +head -c +# 显示开头指定个数的字符数 + +head -n 4 +# 显示多个文件内容,并且只显示前 4 行 + +head -q +# 显示多文件时,不显示文件名字信息 + +cat -n | head -n +# 显示开头指定行数和行好 + +``` + +## tail + +用于查看文档的后N行或持续刷新内容。 + +```sh +tail +# 显示文件末尾 10 行 + +tail -n +# 显示文件末尾指定行数 + +tail -c +# 显示文件的尾部特定个字节内容 + +tail -q +# 当有多个文件参数时,不输出各个文件名 + +tail -v +# 总是输出文件名 + +tail -f +# 实时追踪文件最新内容! +# ctrl+c 可退出 + +tail -f -n 2 /var/log/messages +# 查看 /var/log/messages 系统日志文件的最新 2 行,并保持实时刷新。 + + +cat -n | tail -n +# 显示文件末尾指定行首和行号 +``` + + +## stat + +用来显示文件的详细信息(元信息),比如 + +- inode 索引节点(Linux 中文件的唯一标识) +- Access 权限 +- atime 访问时间 +- mtime 修改时间 +- ctime 状态更改时间 + +```sh +stat +``` + + +## wc + +用于统计指定文本的行数、字数、字节数。 + + +```sh +wc -l +# 只显示行数 + +wc -w +# 只显示单词数 + +wc -c +# 只显示字符数 + +wc -cwl +# 依次输出:行数、单词数、字符数 +``` + + + + +## diff + +该命令用于比较文件的差异。diff命令以逐行的方式,比较文本文件的异同处。如果指定要比较目录,则diff会比较目录中相同文件名的文件,但不会比较其中子目录。 + +```sh +diff [参数] [文件或目录1] [文件或目录2] +# -<行数> 指定要显示多少行的文本。此参数必须与-c或-u参数一并使用。 +# -c 显示全部内文,并标出不同之处。 +# -u 以合并的方式来显示文件内容的不同。 +# -a diff预设只会逐行比较文本文件。 +# -b 不检查空格字符的不同。 +# -d 使用不同的演算法,以较小的单位来做比较。 +# -i 不检查大小写的不同。 +# -y 以并列的方式显示文件的异同之处。 +# -W<宽度> 在使用-y参数时,指定栏宽。 + +diff test1.txt test2.txt -y -W 50 +# 比较test1.txt文件和test2.txt文件,以并排格式输出。 + +``` + +```sh +diff +# 比较文件差异。 + +# 举例: +$ echo -e '第一行\n第二行\n我是log1第3行\n第四行\n第五行\n第六行' > 1.log +$ cat 1.log +第一行 +第二行 +我是log1第3行 +第四行 +第五行 +第六行 +$ echo -e '第一行\n第二行\n我是log2第3行\n第四行' > 2.log +第一行 +第二行 +我是log2第3行 +第四行 +$ diff 1.log 2.log +3c3 +< 我是log1第3行 +--- +> 我是log2第3行 +5,6d4 +< 第五行 +< 第六行 +# 对比结果中的 3c3 表示两个文件在第3行有不同, +# 5,6d4 表示 2.log 文件相比 1.log 文件在第 4 行处开始少了 1.log 文件的第 5 和第 6 行。 + +# ------------ 举例完毕 +``` + +## cmp + +该命令用于比较两个文件是否有差异。当相互比较的两个文件完全一样时,该指令不会显示任何信息。否则会标示出第一个不同之处的字符和列数编号。当不指定任何文件名称,或文件名为"-",则cmp指令会从标准输入设备读取数据。 + +```sh +cmp [-clsv][-i <字符数目>][--help][第一个文件][第二个文件] +# -c 除了标明差异处的十进制字码之外,一并显示该字符所对应字符。 +# -i <字符数目> 指定一个数目。 +# -l 标示出所有不一样的地方。 +# -s 不显示错误信息。 +# -v 显示版本信息。 +# --help 在线帮助。 +``` + +## grep + +grep (Global Regular Expression Print) 根据正则查找字符串并打印 + +在Shell脚本中,grep通过返回一个状态值来表示搜索的状态: + +- `0` 匹配成功。 +- `1` 匹配失败。 +- `2` 搜索的文件不存在。 + +```sh +grep [