简介
正如课程名字所言:“计算机教学中消失的一个学期”,这门课将会教会你许多大学的课堂上不会涉及但却对每个 CSer 无比重要的工具或者知识点。例如 Shell 编程、命令行配置、Git、Vim、tmux、ssh 等等,是迈向极客之路的奠基之课。
01 Course overview + the shell
课程讲义:Course overview + the shell · Missing Semester (mit.edu)
技巧
- 在Shell中需要使用带空格的字符串(如创建文件夹时),可以使用单引号’
('My Movie')
或 双引号"("My Movie")
,或者 \(My\ Movie)
。 - 使用
xdg-open filename
会在系统中查找合适的程序打开文件,如xdg-open .
会以图形化窗口的文件夹打开当前目录。 - 使用
xargs
可以将STDIN作为命令的输入,如ls | xargs -d '\n' tar -cvf backup.tgz
会将当前目录下的所有文件打包,并且可以打包文件名中含有空格的文件(使用-d '\n'
参数)
新知识
环境变量PATH
- Shell会搜索PATH中的路径,找到用户在Shell中敲入的命令并执行它。
- 可以使用
echo $PATH
来查看PATH中包含的路径,这些路径以:分隔 - 可以修改PATH中的路径,从而修改Shell查找命令的路径
目录的权限
r
:是否可以查看目录下的文件w
:是否可以修改目录下的文件名(包括删除该文件)x
:是否可以进入该目录
管道的权限
$ cd /sys/class/backlight/thinkpad_screen
$ sudo echo 3 > brightness
An error occurred while redirecting file 'brightness'
open: Permission denied
Permission denied的原因
|
>
<
这样的操作符,是由Shell执行的,而不是echo
- 此处
echo
是具有root权限的,而Shell只有用户级权限 - 执行顺序是,用户级的Shell使用
>
准备打开brightness文件,然后echo
再进行写入 - 因为用户级的Shell没有打开系统目录下brightness文件的权限,因此提示Permission denied
02 Shell Tools and Scripting
Shell脚本
Shell脚本中的 单引号'
和 双引号"
单引号括起来的字符串中的变量不会展开(即被变量值替换),而双引号中的会展开
foo=bar
echo "$foo"
# prints bar
echo '$foo'
# prints $foo
Shell脚本的参数
$0
:脚本名$1~$9
:脚本的第1~9个参数值$@
:所有的参数$#
:脚本的参数个数$?
:上一个命令的返回值$$
:当前脚本的PID!!
:上一个命令$_
:上一个命令的的最后一个参数
程序的返回状态
- 程序执行成功,返回0;执行失败,返回0以外的错误码
- 可以对程序的返回状态作逻辑运算,但需要注意的是,逻辑运算不是以程序的返回值,而是以程序是否成功执行来进行运算的。即执行成功时返回的0表示true;执行失败时返回的1表示false。
- Linux内置
true
、false
两个程序:true
固定返回程序执行成功时的返回值0,false
固定返回程序执行失败时的返回值1。可以通过执行true
或false
,然后执行echo $?
来查看这个两个的程序的运行结果 - 逻辑运算
&&
和||
是短路运算符,即只要前面的程序执行状态能确定最终的逻辑运算结果,就不执行后面的程序
false || echo "Oops, fail"
# Oops, fail
true || echo "Will not be printed"
#
true && echo "Things went well"
# Things went well
false && echo "Will not be printed"
#
true ; echo "This will always run"
# This will always run
false ; echo "This will always run"
# This will always run
获取命令的输出
命令替换
$( CMD )
会直接将命令的输出替换到使用它的地方;如遍历当前目录下的内容 for file in $( ls )
进程替换
<( CMD )
会将命令的输出保存到临时文件中,再将临时文件中的内容输入重定向给使用它的地方;适用于想要从文件中获取值而非从STDIN中获取值的情况。如 diff <( ls foo ) <( ls bar )
会比较foo和bar文件夹下文件的异同
{}的扩展
在{}
中可以放入一系类的值,这些值会在执行时自动扩展。如:
convert image.{png,jpg}
# Will expand to
convert image.png image.jpg
mkdir foo bar
# This creates files foo/a, foo/b, ... foo/h, bar/a, bar/b, ... bar/h
touch {foo,bar}/{a..h}
touch foo/x bar/y
# Show differences between files in foo and bar
diff <(ls foo) <(ls bar)
# Outputs
# < x
# ---
# > y
Shebang Line
- 格式:由
#!
开头,并构成字符序列#! xx/xx/x
- 位置:通常位于脚本文件的第一行
- 功能:在程序运行的时候,让程序载入器将#!后面的内容,作为解释器指令,并调用该指令
- 问题:需要再写脚本时指定解释器在计算机中的路径,如果在其他电脑上运行该脚本,则可能因为找不到解释器而无法执行脚本,换言之,其可移植性不高
- 提高可移植性的办法:使用
env
命令,该命令会在PATH中查找解释器路径,并直接调用对应的解释器。如#!/usr/bin/env python
Shell脚本综合示例
#!/bin/bash
# 1. iterate through the arguments provide
# 2. grep for the string foobar
# 3. append it to the file as a comment if it’s not found
echo "Starting program at $(date)" # Date will be substituted
echo "Running program $0 with $# arguments with pid $$"
for file in "$@"; do
grep foobar "$file" > /dev/null 2> /dev/null
# When pattern is not found, grep has exit status 1
# We redirect STDOUT and STDERR(用2表示) to a null register since we do not care about them
if [[ $? -ne 0 ]]; then
echo "File $file does not have any foobar, adding one"
echo "# foobar" >> "$file"
fi
done
Shell函数的编写
格式
[ function ] funname [()]
{
action;
[return int;]
}
使用
- 可以直接在Shell命令行中输入
function func()
来创建函数并直接调用,如
system@ubuntu:~$ function test()
> {
> echo "Hello World"
> }
system@ubuntu:~$ test
Hello World
- 可以在Shell脚本中编写函数,使用
source
命令载入(重载)包含该函数的脚本文件后,即可在Shell中调用该函数
函数 VS 脚本
对比点 | 函数 | 脚本 | 备注 |
---|---|---|---|
编程语言 | 只能是同一编程语言 | 可以由不同的编程语言编写 | 对于脚本而言Shebang Line很重要,因为它指示了该脚本的编程语言解释器位置 |
加载 | 一旦定义被读取就加载 | 每次被执行时加载 | 1. 函数比脚本加载得稍微快一些 2. 每次修改函数,都需要重新加载其定义 |
运行环境 | 运行在当前的Shell环境中 | 在自己的进程中执行(会创建自己的进程) | 1. 函数能修改环境变量,如修改当前工作目录 2. 脚本只能通过 export 导出环境变量的值 |
Shell工具
命令帮助文档
tldr:列出该命令的常用参数使用方式,而不会像man
或--help
一样对每个参数做大量的说明
查找文件
递归查找符合标准的文件
# Find all directories named src
find . -name src -type d
# Find all python files that have a folder named test in their path
find . -path '*/test/*.py' -type f
# Find all files modified in the last day
find . -mtime -1
# Find all zip files with size in range 500k to 10M
find . -size +500k -size -10M -name '*.tar.gz'
对查找到的文件做指定操作
# Delete all files with .tmp extension
find . -name '*.tmp' -exec rm {} \;
# Find all PNG files and convert them to JPG
find . -name '*.png' -exec convert {} {}.jpg \;
find命令的替代命令
fd命令
- 提供颜色化的输出、默认正则匹配、Unicode支持等
- 使用简单、快速、用户友好,如搜索文件时只需要
fd pattern
locate命令
- 建立以文件名为索引的数据库进行文件查找
- 使用
updatedb
更新数据库 - find、fd等命令可以使用如修改日期、文件大小、文件权限等属性进行查找,而locate只能基于文件名进行查找
文本查找grep命令
语法
grep [options] pattern [files]
- pattern - 表示要查找的字符串或正则表达式
- files - 表示要查找的文件名,可以同时查找多个文件,如果省略 files 参数,则默认从标准输入中读取数据
常用参数
-i
:忽略大小写进行匹配。-v
:反向查找,只打印不匹配的行。-n
:显示匹配行的行号。-r
:递归查找子目录中的文件。-l
:只打印匹配的文件名。-c
:只打印匹配的行数。
03 Editors (Vim)
学习新编辑器的方法
- 从一个教程开始
- 使用这个编辑器完成所有的文字编辑工作
- 不断精进,寻求更好更高效的处理方式
Vim基础
Buffers, Tabs, and Windows
Buffers
:VIM打开的一系列文件Tabs
:标签页Windows
:基本上就是一个阅读用的窗口
三者之间的关系
一个Buffer可能会在多个Windows中打开,而这多个Windows又处于同一个Tab下
VIM指令
:e {name of file}
:打开文件并编辑:ls
:显示打开的buffers:help {topic}
:打开帮助文档:help :w
:打开:w
的帮助文档:help w
:打开w
动作的帮助文档
VIM分屏
:sp {name of file}
:水平分屏打开新文件:vsp {name of file}
:竖直分屏打开新文件Ctrl + w
+w
:切换分屏Ctrl + w + N + >
:向右加宽NCtrl + w + N + <
:向左加宽N:only
:仅保留当前分屏:hide
:关闭当前分屏:quit
:退出当前分屏
VIM标签页
:tabnew
:打开新的标签页,可以在该标签页下输入命令以创建或打开文件:tabfind {name of file}
:搜索并在新标签页中打开文件:tabs
:显示已打开标签页的列表:tabclose
:关闭当前标签页:tabonly
:仅保留当前标签页:tabn
/gt
:切换到下一标签页:tabp
/gT
:切换到上一标签页:tabm {index}
:将当前标签页放置到指定位置,index从0开始:tabfirst
:切换到第一个标签页:tablast
:切换到最后一个标签页
VIM交互
移动
- 单词
w
:下一个单词b
:单词的开头e
:单词的结尾
- 行
0
:行首$
:行尾^
:第一个非空字符
- 屏幕
H
:光标移动到这个屏幕的最上方那一行的第一个字符M
:光标移动到这个屏幕的中央那一行的第一个字符L
:光标移动到这个屏幕的最下方那一行的第一个字符
- 滚动
Ctrl + f
:屏幕向下滚动一页Ctrl + b
:屏幕向上滚动一页Ctrl + d
:屏幕向下滚动半页Ctrl + u
:屏幕向上滚动半页
- 跳转:
%
跳转到匹配的另一个括号 - 搜索
/{regex}
:可以使用正则表达式进行搜索n
:下一个符合的匹配项N
:上一个符合的匹配项
- 替换
%s/foo/bar/g
:将文件中所有的foo替换为barline_a,line_bs/foo/bar/g
:将line_a到line_b范围内的foo替换为bar
- 复制
y
:在当前文件中复制"+y
:跨文件复制
编辑符
与动作搭配使用的编辑符:i
表示内部,a
表示周遭
ci(
:改变当前()
内部的内容,不包括(
da'
:删除'
的内容,包括'
VIM配置
配置文件
- Linux环境下编辑(创建) ~/.vimrc文件
- Windows环境下在VIM的安装目录下,编辑(创建) _vimrc文件
常见配置
" 设置行号
set number
" 显示相对行号
set relativenumber
" 语法高亮
syntax enable
" 设置gvim的字体
set guifont=Consolas:h12
" 设置自动缩进
set autoindent
" 按下Tab键后,vim显示的空格数
set tabstop=4
" Normal模式下,缩进的空格数
" >> 增加一级缩进
" << 取消一级缩进
" == 取消全部缩进
set shiftwidth=4
" 自动将Tab转为空格
set expandtab
" 一个Tab转为多少空格
set softtabstop=4
" 高亮光标所在行
set cursorline
" 设置高亮号颜色
" Vim识别三种不同的终端:term,黑白终端;cterm,彩色终端;gui,Gvim窗口
" (c)term,可以定义其字体显示为:bold、underline、reverse、italic或standout,用逗号可以组合使用这些属性
" ctermfg设置前景色,ctermbg设置背景色
hi CursorLine cterm=NONE ctermbg=darkred ctermfg=white guibg=darkred guifg=white
" 自动高亮另一半括号
set showmatch
" 在底部显示当前键入的命令
set showcmd
" 高亮搜索结果
set hlsearch
" 命令模式下,底部操作指令按下Tab键自动补全
" 第一次按下Tab,显示所有匹配的操作指令
" 第二次按下Tab,会依次选择各个指令
set wildmenu
set wildmode=longest:list,full
" 设置文件的编码格式
set encoding=utf-8
set termencoding=utf-8
set fileencoding=utf-8
set fileencodings=ucs-bom,utf-8,chinese,cp936
" 支持在Visual模式下,通过C-y复制到系统剪切板
vnoremap <C-y> "+y
" 支持在normal模式下,通过C-p粘贴系统剪切板
nnoremap <C-p> "*p
在其他软件中使用VIM模式
Shell
- Bash:
set -o vi
- Zsh:
bindkey -v
- Fish:
fish_vi_key_bindings
ReadLine
对于使用GUN Readline的软件,如Python REPL。
向~/.inputrc
文件中添加 set editing-mode vi
04 Data Wrangling
课程讲义:Data Wrangling · Missing Semester (mit.edu)
参考资料:Linux文本三剑客超详细教程—grep、sed、awk - alonghub - 博客园 (cnblogs.com)
正则表达式
数据挖掘
grep
简介
- 全称:Global Regular Expression Print
- 功能:能使用正则表达式搜索文本,并把匹配的行打印出来(匹配到的标红)
- 工作方式:
- 在一个或多个文件中搜索字符串模板。
- 如果模板包括空格,则必须被引用,模板后的所有字符串被看作文件名。
- 搜索的结果被送到标准输出,不影响原文件内容。
- 返回值
返回值 | 说明 |
---|---|
0 | 搜索成功 |
1 | 搜索失败 |
2 | 搜索的文件不存在 |
命令格式
grep [option] pattern file
命令参数
- -A<显示行数>:除了显示符合范本样式的那一列之外,并显示该行之后的内容。
- -B<显示行数>:除了显示符合样式的那一行之外,并显示该行之前的内容。
- -C<显示行数>:除了显示符合样式的那一行之外,并显示该行之前后的内容。
- -c:统计匹配的行数
- -e :实现多个pattern间的逻辑or关系
- -E:扩展的正则表达式
- -f FILE:从FILE获取PATTERN匹配
- -F :相当于fgrep
- -i --ignore-case #忽略字符大小写的差别。
- -n:显示匹配的行号
- -o:仅显示匹配到的字符串
- -q: 静默模式,不输出任何信息
- -s:不显示错误信息。
- -v:显示不被pattern 匹配到的行,相当于[^] 反向匹配
- -w :匹配 整个单词
sed
简介
- sed是一个“流编辑器”,一次处理一行内容
- 处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(patternspace )
- 用sed 命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。然后读入下行,执行下一个循环,不断重复,直到文件末尾。文件内容并没有改变,除非使用重定向存储输出或-i
功能
用来自动编辑一个或多个文件, 简化对文件的反复操作
命令格式
sed [options] '[地址定届] command' file(s)
常用options
-n
:不输出模式空间内容到屏幕,即不自动打印,只打印匹配到的行-e
:多点编辑,对每行处理时,可以有多个Script-f
:把Script写到文件当中,在执行sed时-f 指定文件路径,如果是多个Script,换行写-r
:支持扩展的正则表达式-i
:直接将处理的结果写入文件-i.bak
:在将处理的结果写入文件之前备份一份
地址定界
- 不给地址:对全文进行处理
- 单地址
#(行号)
:指定的行/pattern/
:被pattern所能匹配到的每一行
- 地址范围
#1,#2
:从第#1行到第#2行/pattern1/, /pattern2/
:从第一个模式到第二个模式#(行号) , /pattern/
:从第#行到匹配到的模式
- ~:步进
sed -n '1~2p'
:从第一行开始,一次加两行;即只打印奇数行sed -n '2~2p'
:从第二行开始,一次加两行;即只打印偶数行
编辑命令command
d
:删除模式空间匹配的行,并立即启用下一轮循环p
:打印当前模式空间内容,追加到默认输出之后a
:在指定行后追加文本,支持使用\n
实现多行追加i
:在行前面插入文本,支持使用\n
实现多行插入c
:替换行为单行或多行文本,支持使用\n
实现多行替换w
:保存模式匹配的行到指定文件r
:读取指定文件的文本到模式空间中匹配的行后=
:为模式空间中的行打印行号!
:模式空间中匹配的行取反处理s///参数
:查找并替换,以下是常用参数,参数添加在末尾g
:全局行内替换l
:下个字符转换成小写L
:全部匹配到的文本替换为小写u
:下个字符转换为大写U
:全部匹配到的文本替换为大写E
:停止大小写转换
05 Command-line Environment
参考文献
- A Quick and Easy Guide to tmux (hamvocke.com)
- Linux Command Line Adventure: Terminal Multiplexers
- Shell startup scripts — flowblok’s blog
- GitHub does dotfiles - dotfiles.github.io
- SSH端口转发(port forwarding)基础知识_胡小白的数据科学之路的博客-CSDN博客
- Mosh: the mobile shell
- zsh 安装与配置:9步打造高效命令行 - 知乎 (zhihu.com)
- A look at terminal emulators, part 1 - anarcat
Job Control
Killing a process
Signal
Unix系统用于进程间交流信息,使用man 7 signal
可以查看对于各个信号的详细说明
发送信号
SIGINT
:Ctrl + CSIGQUIT
:Ctrl + \kill -SIGNAL(或Signal ID) PID
:kill命令可以指定发送特定信号,默认发送SIGTREM
kill -l
:显示kill
所能发送的信号列表
SIGHUP
:关闭终端或关闭远程连接时发送,会导致进程挂起,即不再运行nohup
:在命令前添加,不响应SIGHUP
信号disown
:在命令已经在运行时使用,不响应SIGHUP
信号- 语法:
disown [参数] [标识符or进程ID]
-h
:标记每个作业标识符,这些作业将不会在shell接收到sighup信号时接收到sighup信号-a
:移除所有的作业-r
:移除运行的作业
- 语法:
SIGKILL
:立即终止进程,不可被进程捕获;缺点在于会留下孤儿进程
进程管理
-
Ctrl + Z
或SIGSTOP
:暂停进程 -
fg
:在前台继续进程 -
bg
:在后台继续进程 -
commands &
:使命令在后台运行,但仍会占用Shell的STDOUT
,这种情况下可以使用Shell的重定向解决 -
jobs
:查看当前命令行会话正在运行的进程jobs
会为进程分配作业编号,通过%作业编号
可以指定进程jobs -l
可以显示进程的pid
终端复用器(Terminal Multiplexers)
优点
- 与多个Shell会话交互
- 与当前的Shell会话断开,在后续某个时间点恢复会话,会话中的命令无需使用
nohup
,因此在远程连接时尤为有用
tmux
近来较为流行的终端复用器工具,除此之外还有screen
工具。
tmux的相关概念以及对应的快捷键如下:
会话(Sessions)
带有一到多个窗口的、独立的工作空间
tmux
:开启一个新的会话tmux new -s NAME
:开启一个新会话并为其命名tmux ls
:列出当前会话- 在
tmux
内,使用<Ctrl + B> d
断开当前会话 tmux -a
:恢复最后一个会话tmux -t FLAG
:恢复指定会话
视窗(Windows)
类似浏览器的标签页(Tabs),是同一会话的不同可视部分
<Ctrl + B> c
:创建一个新的视窗。使用Ctrl + D
关闭<Ctrl + B> N
:跳转到第N个视窗<Ctrl + B> n
:跳转到下一个视窗<Ctrl + B> p
:跳转到上一个视窗<Ctrl + B> ,
:重命名当前视窗<Ctrl + B> w
:列出当前视窗
面板(Panes)
类似Vim的分屏
<Ctrl + B> "
:水平分屏<Ctrl + B> %
:垂直分屏<Ctrl + B> 方向键
:使用键盘上的方向键向指定方向移动面板<Ctrl + B> z
:将当前面板扩大到整个屏幕
Dotfiles
别名(alias命令)
alias
可以为常用的 命令 + 选项 + 参数 创建一个简短、自定义的别名,从而能够提高我们输入、使用命令的效率。
但alias
命令在Shell中创建的别名在Shell关闭后就会消失,如果想要将这些别名永久化,需要写入到配置文件中
常用
alias gs="git satus"
alias ga="git add ."
alias gc="git commit -m"
alias gp="git pull"
其他
unalias alias_name
:禁用某个别名alias alias_name
:查看某个别名的定义
配置文件
常见配置文件路径
bash
:~/.bashrc
,~/.bash_profile
git
:~/.gitconfig
vim
:~/.vimrc
,~/.vim
文件夹ssh
:~/.ssh/config
tmux
:~/.tmux.conf
配置文件的管理
使用版本控制工具管理配置文件,通过脚本将配置文件链接到对应的文件夹下。
具有以下好处:
- 易于安装
- 便携
- 多端同步
- 变更追踪
配置文件脚本
# 判断Shell类型,特定类型的Shell可能具有某些特性
if [[ "$SHELL" == "zsh" ]];
then
{do something};
fi
# 针对特定主机进行个性化定制
if [[ "$(hostname)" == "myServer" ]];
then
{do something};
fi
# 引用外部的文件
if [ -f ~/.alias ];
then
source ~/.alias
fi
Remote Machines
连接
ssh victor@bar.mit.edu
-
用户名:victor
-
服务器:
bar.mit.edu
,或IP地址
执行命令
ssh victor@server ls | grep PATTERN
在远程执行ls
命令,在本地执行grep
命令
ls | ssh victor@server grep PATTERN
在本地执行ls
命令,在远程执行grep
命令
SSH密钥
使用密钥对进行授权,服务器持有公钥,用户主机持有私钥,通过使用SSH密钥可以避免在每次连接服务器时都输入密码的麻烦。
生成密钥
ssh-keygen -o -a 100 -t ed25519 -f ~/.ssh/id_ed25519
密钥授权
面向服务器进行密钥授权,Gitee或Github的密钥授权详解官方网站
ssh
通过查看.ssh/authorized_keys
来决定允许哪台主机接入,因此可以复制公钥到指定路径
cat .ssh/id_ed25519.pub | ssh foobar@remote 'cat >> ~/.ssh/authorized_keys'
或者可以使用更简单的命令ssh-copy-id
ssh-copy-id -i .ssh/id_ed25519 foobar@remote
从SSH复制文件
-
ssh + tee
:使用ssh
执行命令,并通过tee
将STDIN
的内容写入到文件中。cat localfile | ssh remote_server tee serverfile
-
scp
:可以用于递归复制大量的文件和文件夹scp path/to/local_file remote_host:path/to/remote_file
-
rsync
:在scp
的基础中做了改进- 可以检测相同文件,对于相同文件不会进行复制
--partial
:支持断点续传- 使用语法与
scp
类似
端口转发
介绍
每个程序或者应用都会运行在一个指定的端口下(port), 我们可以直接通过监听(listen)该端口来查看其他用户向该应用发出的请求(request)并做出响应(response)。
本地转发(Local port forwarding):发送给本地端口的请求转发到远程机器上
远程转发(Remote port forwarding):远程机器监听请求并将请求转发给本地机器来做出响应
转发命令的常用参数
-N
:这个 SSH 连接只进行端口转发,不登录远程 Shell,不能执行远程命令,只能充当隧道。-f
:后台运行SSH隧道,即使关闭创建隧道时所使用的SSH会话,对应的SSH隧道也不会消失。
本地转发
说明
目标主机是SSH终点机器:此处的==localhost并不是本地主机,而是远程主机的localhost==
$ ssh -L 123:localhost:456 remote-host
目标主机并不是SSH终点机器,而是当作跳板机
$ ssh -L 123:target-host:456 remote-host
应用
1. 使用性能更好的远程服务器运行代码,在本地进行操作
$ ssh -L 9999:localhost:8888 user@remote_server
user@remote_server
是指定服务器的账户- 可以在本机上进入
localhost:9999
来进行代码的编写,而运行任务会转发给远程服务器
2.VPN
$ ssh -L 8080:internal-server:80 -L 8443:internal-server:443 bastion-host -N
- 某个内网的服务器,端口号为80,443
- 本机端口号为8080,8443
- 内网的服务器无法从外网直接访问,但如果网络管理者为我们开放了一个bastion host提供ssh服务,那么就可以借助它作为跳板机访问内部服务器
远程转发
应用
$ ssh -R 2080:target-server:80 local-host
- 本地主机处于外网,而目标机器或者目标机器与SSH的跳板机均位于内网中
- 只能反过来通过SSH跳板机来发起隧道,即让远端的机器(我们的本地主机)来进行端口转发
- 本地主机端口为2080,内网中的服务端口为80
- 此时,本地机器lcoal-host作为ssh的服务端,必须要安装SSH服务器,才能接受跳板机的远程登录。
SSH配置文件
服务器端:/etc/ssh/sshd_config
客户端:~/.ssh/config
SSH配置文件除了可供ssh
使用外,还可以供scp
,rsync
等工具使用
Host vm
User foobar
HostName 172.16.174.141
Port 2222
IdentityFile ~/.ssh/id_ed25519
LocalForward 9999 localhost:8888
# 配置文件可以使用通配符
Host *.mit.edu
User foobaz
扩展
mosh:移动端的ssh,在scp
的基础上做了改进,修复了bug。目前做到了全平台覆盖。
sshfs:可以将远程服务器的文件夹挂载到本地,然后就可以进行本地操作
Shell相关杂项
zsh
zsh 是一个兼容 bash 的 shell,相较 bash 具有以下优点:
- Tab 补全功能强大。命令、命令参数、文件路径均可以补全。
- 插件丰富。快速输入以前使用过的命令、快速跳转文件夹、显示系统负载这些都可以通过插件实现。
- 主题丰富。
- 可定制性高。
框架
类似主题,但需要注意的是,使用大量框架可能导致Shell启动和运行的速度变慢,因此需要进行取舍权衡。
与zsh
相关的框架有:
终端仿真器
终端仿真器是用于运行Shell的工具,不同的Linux/Unix发行版使用不同的终端仿真器,对所使用的终端仿真器进行选择和个性化配置,可以有效提高开发效率。在选择及配置时应作如下考虑:
- 字体
- 配色方案
- 快捷键
- 标签页/面板 支持
- 回滚设置
- 性能:某些新型终端仿真器,如
Alacritty
和kitty
提供GPU加速
06 Version Control(Git)
参考文档
- good commit messages
- https://git-scm.com/docs/git-config
- https://git-scm.com/docs/gitignore
Git的数据模型
快照(SnapShots)
- 快照:Git通过一系列的快照来保存文件和文件夹的历史集合,快照是最顶层的追踪记录单元。
- blob:文件
- tree:文件夹;
tree
可以将blob
、trees
与其名字相映射,即tree
可以包含其他的tree
以及blob
快照示例
<root> (tree)
|
+- foo (tree)
| |
| + bar.txt (blob, contents = "hello world")
|
+- baz.txt (blob, contents = "git is wonderful")
修改历史(快照之间的关系)
- Git的历史记录是一个有向无环图
- Git的一个快照可能继承自多个父快照,即由多个平行的分支
merge
而成
快照关系图
o <-- o <-- o <-- o <---- o
^ /
\ v
--- o <-- o
数据模型伪代码
// a file is a bunch of bytes
type blob = array<byte>
// a directory contains named files and directories
type tree = map<string, tree | blob>
// a commit has parents, metadata, and the top-level tree
type commit = struct {
parents: array<commit>
author: string
message: string
snapshot: tree
}
Git的存储结构
对象和内容追踪
对象
tree
、blob
或者commit
(快照)
type object = blob | tree | commit
内容关联
Git的数据存储中,所有object
都是通过SHA1- hash
算法来进行内容关联的。
objects = map<string, object>
def store(object):
id = sha1(object)
objects[id] = object
def load(id):
return objects[id]
- Git的
object
通过 哈希值 关联它们所引用的对象,而不是直接包含它们在磁盘上的内容。 - 通过
git cat-file -p HASH_VALUE
即可查看对应的哈希值所关联的内容。
引用
快照对应的哈希值有40位,并不便于人类进行记忆,因此Git提供了便于阅读的、对特定快照的引用。
通过使用引用,可以设置指定快照继承哪些父快照。常用的引用如下:
HEAD
:当前所在的快照master
:最新的快照
暂存区
Git的快照并不是保存当前工作目录的状态,而是使用了暂存区,通过add
选项,可以指定需要将哪些修改保存到下一个快照中,而不是对当前所有修改照单全收。这种处理方式允许我们在开发的时候有更多的选择空间,可以更灵活的进行开发作业。
Git命令
基础使用
git help <command>
:获取Git命令的说明帮助git init
:初始化Git仓库,数据存储在.git
文件夹中git status
:获取当前状态,如未进行追踪的修改等信息git add <filename>
:添加指定文件的修改到暂存区git commit
:创建一个新的快照git log
:显示commit
历史,并携带哈希值等信息git log --all --graph --decorate
:可视化显示历史记录(以有向无环图的形式)git diff <filename>
:显示相对于暂存区的变更内容git diff <revision> <filename>
:显示相对于指定快照的变更内容git checkout <revision>
:改变HEAD
和当前分支
分支与合并
git branch
:列出当前所有分支git branch <name>
:创建新分支git checkout -b <name>
:创建新分支并切换到该分支,等同于git branch <name>; git checkout <name>
git merge <revision>
:将指定分支与当前分支合并git mergetool
:解决合并时的冲突问题
远程
git remote
:列出所有连接的远程仓库git remote add <name> <url>
:添加远程仓库git push <remote> <local branch>:<remote branch>
:提交本地分支的代码到远程仓库指定分支git branch --set-upstream-to=<remote>/<remote branch>
:关联本地分支和远程分支git fetch
:拉取最新的Git仓库内容,但不改变HEAD
指向的快照git pull
:拉取最新的Git仓库内容,并将HEAD
指向最新的快照git clone
:从远程仓库下载Git仓库
撤销
git commit --amend
:修改快照的内容git reset HEAD <file>
:取消对某个文件在暂存区的修改git checkout -- <file>
:丢弃修改
高级
git config
:Git的配置信息git clone --depth=1
:指定克隆的层数,而不是下载整个仓库git add -p
:交互式的暂存git blame
:显示某一行最后由谁进行修改.gitignore
:记录不进行追踪的文件,如生成的可执行文件等。
## 添加公钥
> 参考链接:https://cloud.tencent.com/developer/article/1594769
在使用如Gitee、Github等在线代码托管网站的时候,如果是在Windows系统上,还可以通过git配置文件,配置用户名和密码,避免每次下载和上传都要输入的麻烦,可是在Linux系统上,则需要配置SSH密钥才可以。
### 生成密钥
```bash
ssh-keygen -t rsa -C "xxxx@xxx.com"
添加公钥
ssh会生成公钥和私钥,公钥需要部署到gitee等远程仓库的网站上。使用cat查看公钥(以.pub结尾),将内容复制到gitee等网站提供的ssh密钥页面,添加密钥即可。
cat ~/.ssh/debian11_rsa.pub
添加远程仓库
# 或git@github.com
ssh -T git@gitee.com
添加私钥
我在 添加远程仓库 或 上传文件 的时候,会遇到git@gitee.com: permission denied(publickey)
的报错信息,这是由于没有在本地ssh中添加密钥的缘故。
ssh默认会添加如id_rsa
等名称的密钥,但我自己命名的密钥名为debian11_rsa
,ssh无法搜索到连接gitee所需的密钥,因此会出现无法添加远程仓库的问题,所以此处需要手动添加密钥。
# ssh-add后接生成密钥时选择的密钥路径
ssh-add ~/.ssh/debian_rsa
学习资源
- Pro Git:1~5章介绍Git的基本使用,最后一章讲述了一些有趣、高级的内容
- Oh Shit, Git!?!:如何修复常见的Git错误
- Git for Computer Scientists:使用更少的伪代码和更多的有趣图片解释Git的数据模型
07 Debugging and Profiling
参考资料:
- ANSI转义代码(ansi escape code) - 知乎 (zhihu.com)
- 【Linux】GDB调试教程(新手小白)_爪可摘星辰的博客-CSDN博客
- [pwn]调试:gdb+pwndbg食用指南_breezeO_o的博客-CSDN博客
- Strace – The Sysadmin’s Microscope (oracle.com)
- Premature Optimization (c2.com)
- unix - What do ‘real’, ‘user’ and ‘sys’ mean in the output of time(1)? - Stack Overflow
- https://jvns.ca/blog/2017/12/17/how-do-ruby—python-profilers-work-/
- 内存分析工具:https://valgrind.org/
Debugging
通过printf进行Debugging和Logging
- 通过在代码中添加
printf
语句,可以获取自己需要的程序执行信息,从而查明问题发生的原因 - 相比于简单的添加
printf
语句,使用log机制具有以下好处:- 可以将log输出到文件、端口甚至远程服务器,而不只是标准输出
STDOUT
- 可以对log进行等级设置,并根据等级对log进行过滤,获取自己需要的信息;常见的log等级有:INFO, DEBUG, WARN, ERROR等
- 可以将log输出到文件、端口甚至远程服务器,而不只是标准输出
Log编写示例
设置了Log等级、Log颜色的Python脚本文件:logger.py
$ python logger.py
# Raw output as with just prints
$ python logger.py log
# Log formatted output
$ python logger.py log ERROR
# Print only ERROR levels and above
$ python logger.py color
# Color formatted output
给不同等级的Log添加颜色
在Linux操作系统中,可以通过使用 ANSI转义代码 来对Log进行颜色设置
# 这里[255;0;0]是所要表示颜色的RGB值
echo -e "\e[38;2;255;0;0mThis is red\e[0m"
# 在shell中打印所有的颜色
#!/usr/bin/env bash
for R in $(seq 0 20 255); do
for G in $(seq 0 20 255); do
for B in $(seq 0 20 255); do
printf "\e[38;2;${R};${G};${B}m█\e[0m";
done
done
done
第三方Log
- Log保存路径:
/var/log
- 系统Log:通过守护进程
systemd
进行服务控制- 保存路径:
/var/log/journal
- 查看命令:
journalctl
- 保存路径:
- 内核Log:通过
dmesg
命令查看 lnav
是一个高级的、便于进行小规模log查看的工具
Debug工具
- pdb:Python自带的Debug工具
- gdb
- 不仅可以对C风格的编程语言进行调试,而且可以对任何进程进行堆、栈、寄存器等信息的查看
- 可以安装
pwndbg
来增强gdb的功能
特殊工具
系统调用
通过strace
命令可以跟踪程序进行的系统调用
网络数据包
对网络数据包的分析可以使用以下工具:
tcpdump
Wireshark
静态检查工具
分析(Profilling)
耗时(Timing)
- 实时(Real):从程序开始到结束所耗费的时间,包括其他进程所耗费的时间以及等待时(如等待I/O、网络)所耗费的时间
- 用户(User):CPU运行用户代码所耗费的时间
- 系统(Sys):CPU运行内核代码所耗费的时间
一般而言,用户 加上 系统 即为程序运行所真正耗费的时间;运行程序时在开头添加time
即可查看程序运行所耗费的时间。
CPU
CPU分析器分类
- 跟踪分析器(Tracing Profiler):记录程序中每次函数调用
- 取样分析器(Sampling Profiler):定期(通常是ms级)探测程序运行情况,并记录堆栈信息
命令行工具(Python为例)
-
使用
cProfile
模组,分析每次函数调用的耗时$ python -m cProfile -s tottime SCRIPT.py
-
使用行分析(line Profiler)工具,分析每行代码的耗时。
cProfile
的缺点在于,对于使用的第三方库函数,也会记录耗时,从而导致输出的结果非常冗长,难以获取需要的数据,而使用行分析工具则没有这种苦恼。$ kernprof -l -v SCRIPT.py
内存
对于C/C++而言,未释放的内存就无法再继续使用;而对于拥有内存回收机制的Python而言,通过通过指针申请分配的内存也不会被释放。因此通过内存分析工具分析内存泄露问题是很有必要的。
-
Valgrind:C/C++等语言的内存分析工具
-
memory_profiler
:Python用于内存分析的模组$ python -m memory_profiler SCRIPT.py
事件分析
不是通过对耗时或者内存进行分析,而是对与该程序运行相关的事件进行分析,如页错误数(page faults)等信息。可以使用perf
命令来对程序进行事件分析:
perf list
:列出perf
能追踪的所有事件perf stat COMMAND ARG1 ARG2
:获取与进程或命令相关的不同事件数量perf record COMMAND ARG1 ARG2
:记录命令运行情况,并将统计结果保存到perf.data
文件中perf report
:格式化打印perf.data
中收集到的数据
可视化
火焰图
将统计数据以火焰图的形式进行展示,包括但不限于CPU使用情况、内存占用情况等。如Y轴表示函数调用等级、X轴表示函数调用耗时所生成的图片如下所示:
函数调用图
在Python中可以使用pycallgraph
来绘制函数调用图
资源监测
常规监测
htop
:top
命令的升级版。<F6>
:进程排序t
:树状图显示调用等级h
:切换线程
glances
:拥有更好看的UIdstat
:对所有进程的信息进行统计,能对许多子系统如I/O系统,网络系统的实时资源占用率进行计算。
I/O操作
iotop
能实时显示I/O占用情况,并且能够检查进行重度I/O操作的进程
磁盘使用
du
:显示当前目录下每个文件的磁盘占用情况df
:显示每个分区的磁盘占用情况ncdu
:提供交互式的方式提供目录导航(切换目录)、删除文件或目录等操作
内存使用
free
显示系统已用和空闲的内存大小
打开文件
lsof
列出打开(占用)文件的进程信息
网络连接和设置
ss
:一个机器上的特定端口被哪个进程占用ip
:显示路由、网络设备、接口
网络使用
nethogs
iftop
特殊工具
stress
:测试某个工具的抗压能力hyperfine
:比较两个不同程序的执行效率,如搜索的快慢
08 MetaProgramming
课程讲义:Metaprogramming
参考资料
编译系统(Build System)
原理
定义一系列的目标、依赖 以及如何生成这些目标 和依赖 的规则,编译系统会检测生成目标所需的依赖,并应用规则生成这些依赖;而依赖可能会有其他依赖,因此依赖也可能是一个目标;通过这样的递归过程,编译系统能生成最终目标。
理想的编译系统能避免不必要的规则执行过程,如某些依赖并未发生改变,因此并不需要据此生成新的文件。
Make
最常见的编译系统工具是make
,会自动执行当前目录下命名为makefile
的文件,其使用方式如下:
paper.pdf: paper.tex plot-data.png
pdflatex paper.tex
plot-%.png: %.dat plot.py
./plot.py -i $*.dat -o $@
-
冒号左边的是要生成的目标文件,如paper.pdf、plot-%.png
-
冒号右边的是生成目标文件所需的依赖文件,如paper.tex、plot-data.png
-
被缩进的内容是为生成目标文件所执行的指令
-
%
是通配符,在冒号前后一致,指代同一个字符 -
第一行的是最终目标文件,为添加任何参数时,
make
会生成最终目标文件;但如果使用make plot-data.png
这样的指令,则能指定生成的目标文件
依赖管理
语义化版本
使用语义化版本(semantic versioning)标准,将程序的版本号命名为:major.minor.patch ,有助于进行依赖管理。开发人员借此可以知晓哪些版本可以放心使用,而哪些版本或许不能正常使用。
patch
:新的发布没有影响到APIminor
:添加了可以向后兼容的APImajor
:通过不支持向后兼容的方式修改了API
lock files
lock files 会列出当前所有依赖项的准确版本。
通常开发人员需要准确的运行升级程序来对依赖项进行升级,而采用lock files的好处在于:
- 避免不必要的重复编译
- 可重复构建,即在对外发布的时候,可以通过同一套代码构建出一致的软件包
- 避免自动升级到最新版本,这可能会导致程序崩溃
持续构建系统(Continuous integration systems/CI)
CI系统的主要作用在于,当代码发生改变时,自动相应的动作。如格式检测、推送到Pip仓库等等。
常见的CI系统有:Travis CI、Azure Pipelines、GitHub Actions
09 Security and Cryptography
课程讲义:Security and Cryptography
参考资料
熵
- 熵通过bits来衡量,用于表示从一系列可能性中随机选取的概率。熵的计算公式为:
log~2~(可能性的数量)
- 攻击者知道的是密码的模式,但并不知道随机选择密码的随机性。可以通过物理手段,如掷骰子来进行随机选择字典中的某一项来使得随机性最大,而不是个人凭直观的选择。因为人类并不能做到完全的随机,很多时候的选择是有迹可循的,而这点可能会被攻击者利用。
- 密码的熵为多少比较合适呢?一般而言熵为40左右的密码比较好;但如果要预防离线攻击,80左右才比较合适。
哈希函数
哈希函数可以将任意大小的输入转化为固定长度的输出,例如Git使用的SHA1函数,可以将任意长度的输入转化为160bit(40个16进制数),可以使用sha1sum
命令调用SHA1函数。
$ printf 'hello' | sha1sum
aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
(加密用)哈希函数一般应该具有以下特点:
- 决定性(Deterministic):相同的输入会生成相同的输出
- 不可逆性(Non-invertible):难以通过所需的输出倒推寻找到其输入
- 目标冲突抵抗性(Target collision resistant):对于指定的输入m_1,难以找到另一个输入m_2,它们会生成同样的输出
- 冲突抵抗性(Collision resistant):难以找到两个不同的输入m_1,m_2,它们具有相同的输出。这点要比Target collision resistant 更严格
应用
- Git,用于内容地址存储(content-addressed storage)。问题在于,既然有非加密用哈希函数,为什么Git要使用加密用哈希函数呢?
- 文件内容的简介。如对软件的安装包使用哈希函数生成哈希值,通过校验官网提供的软件哈希值与自己从第三方镜像网站下载软件的哈希值,即可知道自己下载的是否为原装正版。
密钥派生函数(KDF-Key derivation functions)
KDF一般用于为其他加密算法生成固定长度的密钥以供使用。
通常KDF会被故意设计的较慢,用以延缓线下暴力破解。
应用
- 为其他加密算法生成密钥
- 强化登录凭证:在存储密码时,添加一些随机生成的盐 进去,存储的是
KDF(password + salt)
。在验证登录时,使用存储的盐 逆运算KDF,得到密码进行验证。
对称加密(Symmetric cryptography)
当前最常用的对称加密算法是AES算法。对称加密算法实现了如下的几个函数:
keygen() -> key (this function is randomized)
encrypt(plaintext, key) -> ciphertext (密文)
decrypt(ciphertext, key) -> plaintext (明文)
非对称加密(Asymmetric cryptography)
非对称加密分为公钥 和私钥。公钥可以发布到互联网上,不会对信息的安全造成影响;私钥需要私人管理。
非对称加密实现了如下的函数,来进行加/解密,或 签名/验证。
keygen() -> key (this function is randomized)
encrypt(plaintext, key) -> ciphertext (密文)
decrypt(ciphertext, key) -> plaintext (明文)
sign(message, private key) -> signature (签名)
verify(message, signature, public key) -> bool (签名是否有效)
应用
- PGP邮件加密:人们可以在PGP密钥服务器或Keybase上发布他们的公钥,这样任何人就可以向他们发送加密邮件
- 私密信息:使用非对称加密来建立私密通信。由于非对称加密算法比较耗时,通常做法是使用对称加密算法生成密钥,通过非对称加密传输密钥,之后就可以使用加/解密较快的对称加密算法建立私密通信了。
- 软件签名:Git有GPG签名的commit和tag,人们可以通过公布的公钥来验证下载的软件是否为原装正版。
密钥分配
在网上公布的公钥可能会被人恶意篡改,如何将网络上的公钥与现实世界中的身份对应是一个难题,以下是一些软件的解决方案:
- 第一个人可信,第一个人相信的人也就因此可信。信任链可以传播。
- 可信的网站
- 公证机构
案例学习
密码管理器
使用密码管理器能避免密码的重复使用,密码管理器会使用KDF自动生成高熵密钥并进行加密存储,用户只需要记住一个高熵的密码即可。
双重验证
双重验证要求用户提供密码以及验证器(如银行的U盾)来进行验证,可以有效预防密码被偷以及钓鱼攻击
磁盘加密
可以在笔记本被偷时保护数据。
- Linux:cryptsetup + LUKS
- Windows:BitLocker
- MacOS:FileVault
SSH
- 使用
ssh-keygen
命令会生成公钥和私钥。 - 公钥可以原样存储,而私钥则应该加密存储,因此
ssh-keygen
会提示用户输入passphrase - 当服务器拥有客户端的公钥(存储在
.ssh/authorized_keys
文件中)时,会使用如下方式验证客户端是否真的拥有私钥:- 服务器向客户端发送任意数字
- 客户端使用私钥对这些任意数字进行签名,并返还签名给服务器
- 服务器使用客户端公钥验证签名。
10 Potpourri
课程讲义:Potpourri
键盘绑定
重新映射(示例)
- 将
Caps Lock
映射为Ctrl
或Escape
,因为CapsLock处于一个便于接触的位置,但使用却并不频繁
触发特定动作(示例)
- 打开新终端或浏览器窗口
- 插入特定的文本,如手机号、邮箱
- 电脑休眠
高级修改(示例)
- 重新映射按键的频率,如连续按压5次
shift
,触发大写锁定 - 重新映射敲击和持续按压,如快速敲击
Caps Lock
触发Escape
,而持续按压则输入大写
软件资源
- MacOS: karabiner-elements, skhd 和 BetterTouchTool
- Linux: xmodmap 和 Autokey
- Windows:内置的控制面板, AutoHotkey或SharpKeys
- QMK:如果键盘支持自定义固件,可以使用QMK修改硬件设置;之后在任何电脑上使用这个键盘时,键盘重新映射都是一样的。
守护进程(Daemons)
守护进程一般在系统后台运行,无需用户启动或与用户进行交互。
守护进程一般以d
结尾,例如sshd
。
Linux守护进程
在Linux系统中,使用systemd
来运行、设置守护进程,可以使用systemctl
命令来对系统中运行的守护进程进行查看、设置:
status
:列出当前正在运行的守护进程enabel
disable
start
restart
stop
添加、配置守护进程
# /etc/systemd/system/myapp.service
[Unit]
Description=My Custom App
After=network.target
[Service]
User=foo
Group=foo
WorkingDirectory=/home/foo/projects/mydaemon
ExecStart=/usr/bin/local/python3.7 app.py
Restart=on-failure
[Install]
WantedBy=multi-user.target
FUSE(Filesystem in User Space)
Linux系统使用VFS来统一管理不同的文件系统,如ext4、ntfs等。当用户对文件系统进行操作,如touch a
时,VFS会将系统调用传递到内核,内核再根据具体的文件系统调用特定的函数进行文件的创建操作。
也就是说,文件的创建过程是在内核层面的,用户不可干涉。
而FUSE则允许当发送文件系统调用时运行用户层面的代码,然后将必要的系统调用传递给内核。即对于文件系统调用,用户可以执行任意的操作,如向特定邮箱发送提醒等。
FUSE实例
- sshfs:通过SSH连接在本地打开远程文件
- rclone:将Dropbox、谷歌云存储等云盘挂载到本地
- gocryptfs:加密整文件系统,文件被加密存储,但当文件系统挂载时,可以像明文一样被打开
- kbsf:对文件系统进行分区加密,可以有私人、共享、公开文件夹
备份
误区
- 将数据在同一个磁盘上复制一份:因为磁盘一旦损坏,所有数据都会丢失
- 将数据在另一个磁盘上复制一份:因为磁盘可能会丢失、被偷
- 云同步:虽然方便,但一旦云存储数据被抹除、篡改,其影响会传播,即你所存储的数据可能会受到影响
策略
-
版本化:可以方便的查看修改历史以及从历史中恢复
-
去重:只保存修改或新增的内存,避免重复存储
-
安全:需要考虑需要哪些信息才可以将自己的备份删除、篡改,从而选取适当的安全策略
-
离线:将网络应用中重要的信息,如播放列表、照片等离线存储下来,避免由于账号被禁等情况丢失数据
通用命令行标记
--help
:显示简短的使用说明- 许多命令行工具提供
dry run
模式,即展示将要进行的操作,但并不会真的执行这些操作,因此这些操作是可逆的;此外在执行破坏性操作,如删除文件时,会提供交互式标记,来提示用户确认操作 --version/-V
:显示版本信息,在报告bug时很好用--verbose/-v
:显示详细的执行信息。可以使用多个v来输出更详细的信息,如-vvvv
;在进行debug调试时很有用--quiet
:只在有错误发生时打印信息-
:指代标准输入/输出,具体根据工具所需是输入还是输出决定。-r
:递归--
:停止解释命令行标记,注意前后都有空格- 删除名为
-r
的文件:rm -- -r
- 通过ssh执行带参数的命令:
ssh machine -- foo --for-foo
- 删除名为
U盘启动盘(Live USB)
U盘启动盘中包含有一个操作系统,插入U盘启动盘,在Boot界面选择U盘,即可进入U盘启动盘中的操作系统。
U盘启动盘有多种用途,如试运行软件,学习操作系,在电脑重装系统崩溃时修复数据等。
QA
课程讲义:Q&A
推荐优先学习的工具
- 学习如何更多的使用键盘、更少的使用鼠标。如设置快捷键、修改接口等
- 好好学习所使用的编辑器
- 学习如何自动化/简化工作流中的重复性任务
- 学习版本控制系统(如GIT),并结合GitHub学习如何在现代软件项目中与他人进行合作
Pyhon Vs Bah
Shell脚本的特点是为一系列特定的命令编写简短、一次性的脚本,并不适合进行大型软件开发:
- Bash在简单的使用场合能够获得正确的结果,但对于所有可能的输入却很难保证都能获得正确的结果。例如脚本参数中的空格可能会导致数不清的bug
- Bash的复用性并不高,因此很难复用之前写过的脚本;并且在Bash中没有库的概念。
- Bash对于特殊值使用如
$?
来代表,而在其他语言中可以使用exitCode
来表示
source script.sh
和./script.sh
的区别
相同点
- 都会运行
script.sh
这个脚本文件
不同点
- 通过
source
执行的命令会在当前会话中发生作用,如切换目录或定义函数等 - 通过
./script.sh
执行的命令时,当前会话会创建一个子会话,这些命令在子会话中执行,当执行完毕后返回父会话时,并不会对父会话造成任何改变
apt VS pip
建议
对于这个问题并没有准确的答案,但有两条建议:
- 不要同时使用
apt
和pip
安装同一个包,以免导致难以调试的Bug - 尽可能使用编程语言特定的包管理器,并且使用独立环境(如Python的virtualenv)来避免污染系统环境
应该考虑的因素
-
常用的包通过
apt
或pip
都可以获取,但不太常见或较新的包可能只能通过特定语言的包管理器获取。 -
当使用系统的包管理器时,库文件会被在系统范围内进行安装,因此不适用于需要使用同一个包的不同版本的开发需求;相对的,大多数编程语言支持独立的虚拟环境允许按照不同版本的包,并且不同版本之间不会产生冲突,例如python的virtualenv。
-
根据操作系统和硬件架构的不同,某些包可能会带有需要进行编译的二进制文件,如ARM电脑或树莓PI,此时使用系统包管理器要比特定语言的包管理器要好