本文是记录没有sudo权限,但需要在Linux系统上安装Git的过程,并且学习了相关的Makefile和环境变量的知识。这一过程是非常典型的./configure && make && make install 三部曲。如果你有sudo权限,那自然sudo apt-get install git(for Ubuntu/Debian)就好,会自动帮你完成这一系列编译链。

当你在命令行运行 ./configure && make && make install 时,实际上是三个过程

  1. 配置阶段(./configure:检查系统环境并生成 Makefile 文件
  2. 编译阶段(make:根据 Makefile 将源代码编译为二进制文件
  3. 安装阶段(make install:将编译好的文件复制到系统目标目录

使用源码编译安装

1
wget https://github.com/git/git/archive/refs/tags/v2.42.0.tar.gz -O git.tar.gz
  1. 解压源码

    1
    2
    tar -zxvf git.tar.gz
    cd git-*
  2. 安装依赖

    • 编译Git需要一些依赖库,如curlzlib等。你可以通过以下命令安装(无需sudo权限),这个大家一般都有,如果没有权限安装libcurl4-openssl-dev,接着configure make三件套就是:
      1
      2
      3
      apt-get download libcurl4-openssl-dev zlib1g-dev
      dpkg -x libcurl4-openssl-dev*.deb ./
      dpkg -x zlib1g-dev*.deb ./
  3. 编译并安装

    • 编译并安装到自定义目录(例如$HOME/bin):
      1
      2
      3
      ./configure --prefix=$HOME/bin
      make
      make install
    • $HOME/bin/bin添加到你的PATH环境变量中:
      1
      2
      echo 'export PATH=$HOME/bin/bin:$PATH' >> ~/.bashrc
      source ~/.bashrc
  4. 验证安装

    1
    git --version

这上面的过程涉及到了环境变量和Make两个东西,怎么理解呢?我们在Windows系统也都在安装python等工具时设置过环境变量,但可能没有仔细想过。

环境变量

环境变量在操作系统中用来指定操作系统运行环境的一些参数,具体一个环境变量包括名称和参数值。

变量名称 作用
HOME 用户的主目录(即家目录)
SHELL 用户在使用的Shell解释器名称
HISTSIZE 输出的历史命令记录条数
HISTFILESIZE 保存的历史命令记录条数
MAIL 邮件保存路径
LANG 系统语言、语系名称
RANDOM 生成一个随机数字
PS1 Bash解释器的提示符
PATH 定义解释器搜索用户执行命令的路径
EDITOR 用户默认的文本编辑器

printenv或者env,ENV可以把PC所有的环境变量打印出来。

1
2
3
4
5
6
7
8
9
$ printenv  
.....
TERM_PROGRAM=Apple_Terminal
SHELL=/bin/zsh
TERM=xterm-256color
TERM_PROGRAM_VERSION=433
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.MTCZ3F7w1u/Listeners
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
......

可以看到环境变量有这么多,按照key value这种排列出来。

  • SHELL: 用户的默认 shell, 当用户登录时,系统会根据 SHELL 环境变量来确定使用哪个 shell。这个变量影响到用户在终端中运行的命令行环境。

  • CONDA_EXE, CONDA_PYTHON_EXE, CONDA_PREFIX, CONDA_DEFAULT_ENV, CONDA_PROMPT_MODIFIER, CONDA_SHLVL:这些变量与 Anaconda 或 Miniconda(Python 的包管理和环境管理工具)相关。它们提供有关当前 Conda 环境的信息。
    CONDA_EXE: Conda 可执行文件的路径。CONDA_PYTHON_EXE: 当前 Conda 环境中 Python 可执行文件的路径。
    CONDA_PREFIX: 当前 Conda 环境的路径。CONDA_DEFAULT_ENV: 当前活动的 Conda 环境名称。
    CONDA_PROMPT_MODIFIER: 终端提示符中显示的当前 Conda 环境的名称。
    CONDA_SHLVL: 当前 Conda 环境的层级。

  • LC_CTYPE:指定字符集和语言环境的类型。UTF-8 表示使用 UTF-8 编码,支持多种语言字符。

  • _:通常用于保存上一个命令的执行结果。在某些情况下,它会被用作传递给环境的最后命令的参数。

  • ?:返回上一个命令的执行成功与否。成功返回0,未成功返回其他数,可以写if [ $? -eq 0 ]; do functin();fi

    1
    2
    3
    4
    5
    6
    $ echo $LC_CTYPE
    > UTF-8
    $ echo $_
    > UTF-8
    $ echo $?
    > 0

PATH 环境变量

我们来看看当pwd的时候发生了什么事

我们有两种pwd的方式

1
2
$ /usr/bin/pwd  
$ pwd

第一种方法每次都要从根路径自己手打,这样也太没效率了,总不能每次新建一个文件夹都要这样写?/usr/bin/mkdir new_dir。 于是就有了第二种这样,直接把命令打出来,不用自己从根路径一步步的打,那么系统是如何找到pwd这个命令的呢。这就涉及到了PATH这一环境变量。PATH 是一个特殊的环境变量,它包含一系列目录的路径,操作系统在这些目录中查找可执行文件。当你在终端中输入一个命令时,系统会依次在这些目录中搜索相应的可执行文件。

输入下面的命令看一下环境变量下的PATH有哪些路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ echo $PATH  
>
/Users/user1/.julia/bin:
/opt/homebrew/Caskroom/miniconda/base/bin:
/opt/homebrew/Caskroom/miniconda/base/condabin:
/opt/homebrew/bin:
/opt/homebrew/sbin:
/usr/local/bin:
/System/Cryptexes/App/usr/bin:
/usr/bin:
/bin:
/usr/sbin:/sbin:
/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:
/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:
/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:
/Users/user1/.cargo/bin

$ where pwd #$ which pwd # which也可以
> /bin/pwd

于是可以看出这两种方法的区别在于一个是程序自己从PATH里面找出来,一个是你自己输入路径来执行命令。

命令在 Linux 中的执行分为 4 个步骤。

  1. 判断用户是否以绝对路径或相对路径的方式输入命令(如 /bin/ls ),如果是的话则直接执行。
  2. Linux 系统检查用户输入的命令是否为“别名命令”,即用一个自定义的命令名称来替换原本的命令名称。可以用 alias 命令来创建一个属于自己的命令别名,格式为“alias rm= rm -i”或者“alias 别名=命令”,若要取消一个命令别名,则是用 unalias 命令,格式为“unalias 别名”。我们之前在使用 rm 命令删除文件时,Linux 系统都会要求我们再确认是否执行删除操作,其实这就是 Linux 系统为了防止用户误删除文件而特意设置的 rm 别名命令。
  3. Bash 解释器判断用户输入的是内部命令还是外部命令。内部命令是解释器内部的指令,会被直接执行;而用户在绝大部分时间输入的是外部命令,这些命令交由步骤 4 继续处理。可以使用“type 命令名称”来判断用户输入的命令是内部命令还是外部命令。
1
2
3
4
5
6
(base) user@host ~ % type ls
ls is /bin/ls
(base) user@host ~ % type pwd
pwd is a shell builtin
(base) user@host ~ % % type chmod
chmod is /bin/chmod
  1. 系统在多个路径中查找用户输入的命令文件,而定义这些路径的变量叫作 PATH,作用是告诉 Bash 解释器待执行的命令可能存放的位置,然后 Bash 解释器就会乖乖地在这些位置中逐个查找。 PATH 是由多个路径值组成的变量,每个路径值之间用冒号间隔,对这些路径的增加和删除操作将影响到 Bash 解释器对Linux 命令的查找。

如何设置PATH或者是其他环境变量

系统变量就是一个大的容器,其中里面有一个环境变量是PATH,PATH主要用于储存各种命令的路径。那么我们如何设置呢?我们可以用export PATH=/xxx/bin:$PATH这行命令,作用是将 /xxx/bin 目录添加到 PATH 的开头。将新的目录添加到$PATH的开头,意味着优先查找该目录中的可执行文件。如果你希望后面的目录优先被查找,可以将$PATH放在前面,如export PATH=$PATH:/xxx/bin

这个设置只在当前终端会话中有效。如果你想要在每次打开终端时都自动设置,可以将这行命令添加到你的 shell 配置文件中,如 ~/.bashrc~/.bash_profile~/.zshrc, 如果没有执行权限,可以source ~/.bashrc

Linux 文件系统目录结构

上面导出了PATH路径,可以看看这些文件是用来干嘛的。

目录 描述
/bin 基本命令二进制文件
/sbin 基本的系统二进制文件,通常是 root 运行的
/dev 设备文件,通常是硬件设备接口文件
/etc 主机特定的系统配置文件
/home 系统用户的主目录
/lib 系统软件通用库
/opt 可选的应用软件
/sys 包含系统的信息和配置(第一堂课介绍的)
/tmp 临时文件(/var/tmp) 通常重启时删除
/usr/ 只读的用户数据
/usr/bin 非必须的命令二进制文件
/usr/sbin 非必须的系统二进制文件,通常是由 root 运行的
/usr/local/bin 用户编译程序的二进制文件
/var 变量文件 像日志或缓存

Make

大多数Unix系统都会包含一个 “构建过程”,需要执行一系列操作。通常这一过程包含了很多步骤,很多分支。执行一些命令来生成图表,然后执行另外的一些命令生成结果,然后再执行其他的命令来生成最终的论文,这一过程可以通过构建系统来完成,这些系统一般涉及到dependencies, targets, and rules

make是最常用的构建系统之一,您会发现它通常被安装到了几乎所有基于UNIX的系统中。make 并不完美,但是对于中小型项目来说,它已经足够好了。当您执行 make 时,它会去参考当前目录下名为 Makefile 的文件。所有构建目标、相关依赖和规则都需要在该文件中定义。我们先来定义前置的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ cat paper.tex
\documentclass{article}
\usepackage{graphicx}
\begin{document}
\includegraphics[scale=0.65]{plot-data.png}
\end{document}
$ cat plot.py
#!/usr/bin/env python
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-i', type=argparse.FileType('r'))
parser.add_argument('-o')
args = parser.parse_args()

data = np.loadtxt(args.i)
plt.plot(data[:, 0], data[:, 1])
plt.savefig(args.o)
$ cat data.dat
1 1
2 2
3 3
4 4
5 8

然后再定义Makefile文件:

1
2
3
4
5
paper.pdf: paper.tex plot-data.png
pdflatex paper.tex

plot-%.png: %.dat plot.py
./plot.py -i $*.dat -o $@

接下来执行make,看会发生什么?

1
2
3
4
$ make
> ./plot.py -i data.dat -o plot-data.png
> pdflatex paper.tex
... lots of output ...

生成了PDF!如果再次执行 make 会怎样?

1
2
$ make
> make: 'paper.pdf' is up to date.

没做任何变动,因为它什么都不需要做。make 检查出所有之前构建的目标仍然与其列出的依赖项保持最新状态。我们再来看一下这句话:

当你在命令行运行 ./configure && make && make install 时,实际上是三个过程

  1. 配置阶段(./configure:检查系统环境并生成 Makefile 文件
  2. 编译阶段(make:根据 Makefile 将源代码编译为二进制文件
  3. 安装阶段(make install:将编译好的文件复制到系统目标目录

所以我们在下载源文件后配置时,是根据源码包中 Makefile.in 文件的指示,configure脚本可以在编译软件之前检测系统的硬件架构、操作系统类型、所需的库文件以及其他相关的环境信息,根据所采集到的系统信息,自动在当前目录中生成一个用于编译和安装软件的Makefile文件(还有其它本文无需关心的文件),其中包括了编译器选项、依赖库的路径、安装路径等,然后 make 程序就按照当前目录中的 Makefile 文件的指示将源代码编译为二进制文件,最后将这些二进制文件移动(即安装)到指定的地方(仍然按照 Makefile 文件的指示)。
configure脚本通常是通过autoconf工具生成的,autoconf工具会根据用户在configure.ac文件中提供的信息和规则,生成对应的configure脚本。 autoconf -o configure configure.ac

接着安装

  1. 报错!系统中缺少autoconf工具。autoconf是一个用于生成configure脚本的工具,它是许多开源项目(包括Git)编译过程中必需的。所以我们接着三部曲安装autoconf
1
2
3
4
5
6
wget https://ftp.gnu.org/gnu/autoconf/autoconf-2.71.tar.gz
tar -zxvf autoconf-2.71.tar.gz
cd autoconf-2.71
./configure --prefix=$HOME/bin
make
make install
  1. 报错,./configure脚本提示缺少GNU M4工具。GNU M4是一个宏处理器,autoconf和许多其他项目在生成configure脚本时需要它。你需要手动安装GNU M4,因为系统中没有找到合适的版本。
1
2
3
4
5
6
wget https://ftp.gnu.org/gnu/m4/m4-1.4.19.tar.gz
tar -zxvf m4-1.4.19.tar.gz
cd m4-1.4.19
./configure --prefix=$HOME/bin
make
make install
  • 确保GNU M4已经正确安装并添加到PATH中后,重新运行./configure脚本:
    1
    ./configure --prefix=$HOME/bin

如果在后续步骤中遇到其他依赖问题(如gawkcurlopenssl等),可以按照类似的方法手动安装这些工具。事实上我又遇到了zlib.h not found, msgfmt: not found的错误(缺少gettext工具集中的msgfmt命令。msgfmt用于编译国际化消息文件,是Git编译过程中需要的工具之一。), automake, aclocal 未安装的问题,一一解决,成功安装Git。