跳转至

命令行

雖然圖形界面能做的事情越來越多,但有很多高階操作仍然需要使用命令行來解決。

本頁面將簡要介紹命令行的一些使用方法。

基礎

Windows 自帶的命令行界面有兩個。「命令提示符」(cmd)是其中較為古老的一個,功能也相對簡單。PowerShell 是較新的一個命令行界面,自帶的功能豐富,但相對臃腫。兩個界面都可以在開始菜單中找到。

類 Unix 系統(包含 macOS 和 Linux,以下稱為 Unix)分為有圖形界面和無圖形界面兩種情況。如果系統有圖形界面(如使用 macOS 或者在 Linux 下安裝了 GNOME、KDE 等圖形界面),則命令行一般可以通過名為「終端」(Terminal 或 Console)的程序打開。沒有圖形界面的系統會在啓動完成後自動進入命令行。

Windows 下的命令行長這樣:

1
C:\Users\chtholly>

在命令行上輸入的指令會顯示在 > 以後。

1
C:\Users\chtholly>echo "Hello World!"

Unix 下的命令行長這樣(以 Debian/Ubuntu 為例,其它系統的命令行大體類似):

1
chtholly@seniorious:~$

在命令行上輸入的指令會顯示在 $ 以後。

1
chtholly@seniorious:~$ echo "Hello World!"

如果在 Unix 下使用 root 登錄命令行,那麼 $ 會被替換成 #

1
root@seniorious:~# apt-get install gcc

命令行的 >$# 之前會顯示一個路徑,這個路徑就是工作目錄(working directory),或者當前目錄。在 Unix 下當前目錄有時會顯示成類似 ~/folder 的形式,最開頭的 ~ 就是當前登錄的用户的主目錄。用户 chtholly 的主目錄在不同系統下的位置是不同的;在 Linux 下,其主目錄位於 /home/chtholly,而在 macOS 下,其主目錄位於 /Users/chtholly

語法和常用命令1

文件系統相關

先介紹文件系統裏描述位置的兩種方式,相對路徑和絕對路徑。

  • 相對路徑:用相對當前路徑的位置關係來描述位置。例如當前路徑為 ~/folder,則 ./a.cpp 實際上指的就是 ~/folder/a.cpp 這個文件。隨着當前路徑的變化,相對路徑描述的位置也可能發生改變

  • 絕對路徑:用完整的路徑來描述位置。例如 ~/folder/a.cpp 就是一個絕對路徑的例子。絕對路徑描述的位置不隨當前路徑的變化而改變

    Windows/Unix 用 . 代表當前目錄,.. 代表當前目錄的父目錄。特別地,在 Unix 下,用 ~ 表示用户主目錄(注意 ~ 由 shell 展開,因此在其他地方可能不可用)。

在 Windows/Unix 下,使用 pwd 命令可以打印當前的目錄,cd <目錄> 命令可以切換當前的目錄。例如,cd folder 會切換到當前目錄的 folder 子目錄;cd .. 會切換到當前目錄的父目錄。

在 Windows 下,使用 dir 命令可以列出當前目錄的文件列表。在 Unix 下,列出文件列表的命令是 ls。特別的,在 PowerShell 下,可以使用與 Unix 相同的 ls 命令。

在 Windows 下,使用 md <目錄> 或者 mkdir <目錄> 命令創建一個新目錄,使用 rd <目錄> 命令刪除一個目錄。在 Unix 下,這兩個命令分別是 mkdirrmdir。需要注意的是,使用 rd 或是 rmdir 刪除一個目錄前,這個目錄必須是空的。如果想要刪除非空目錄(和該目錄下的所有文件)的話,Unix 下可以執行 rm -r <目錄> 命令,Windows 下可以執行 rd /s <目錄> 命令。

重定向機制

我編譯了一個程序,它從標準輸入讀入,並輸出到標準輸出。然而輸入文件和輸出文件都很大,這時候能不能想辦法把輸入重定向到指定的輸入文件,輸出重定向到指定的輸出文件呢?

使用如下命令即可實現。

1
$ command < input > output

例如,./prog < 1.in > 1.out 這個命令就將讓 prog 這個程序從當前目錄下的 1.in 中讀入數據,並將程序輸出覆蓋寫入到 1.out

Warning

1.out 原本的內容會被覆蓋,如果想要在原輸出文件末尾追加寫入,請使用 >>,即 ./prog >> 1.out 的方式做輸出重定向

注意,PowerShell 只支持輸出重定向,不支持輸入重定向。

事實上,大多數 OJ 都採用了這樣的重定向機制。選手提交的程序採用標準輸入輸出,通過重定向機制,就可以讓選手的程序從給定的輸入文件讀入數據,輸出到指定的輸出文件,再進行文件比較就可以評測了。

執行程序

對於一個可執行程序或是批處理腳本,只需在命令行裏直接輸入它的文件名即可執行它。

當然,執行一個文件時,命令行並不會把所有目錄下的文件都找一遍。環境變量 PATH 描述了命令行搜索路徑的範圍,命令行會在 PATH 中的路徑尋找目標文件。

對於 Windows 系統,當前目錄也在命令行的默認搜索範圍內。例如 Windows 系統中,輸入 hello 命令就可以執行當前目錄下的 hello.exe。但是在 PowerShell 中,PowerShell 默認不會從當前目錄尋找可執行文件(這與在 Unix 的行為一致),因而在 PowerShell 中需要使用相對路徑或絕對路徑調用當前目錄下的可執行文件,例如 .\hello.exe,否則,你將看到以下報錯:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
PS> hello
hello: The term 'hello' is not recognized as a name of a cmdlet,
function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that
the path is correct and try again.

Suggestion [3,General]: The command hello was not found, but does exist
in the current location. PowerShell does not load commands from the
current location by default. If you trust this command, instead type:
".\hello". See "get-help about_Command_Precedence" for more details.

在 Unix 系統中,當前目錄並不在命令行的默認搜索範圍內,所以執行當前目錄下的 hello 程序的命令就變成了 ./hello:

1
2
3
4
$ hello
hello: command not found
$ ./hello
Hello World!

總結

上面介紹的用法只是命令行命令的一小部分,還有很多命令沒有涉及到。在命令行裏輸入幫助命令 help,可以查詢所有基本命令以及它們的用途。

下面給出 Windows 系統和 Unix 系統的命令對照表,以供參考。

分類 Windows 系統 Unix 系統
文件列表 dir ls
切換目錄 cd cd
建立目錄 md mkdir
刪除目錄 rd rmdir
比較文件 fc diff
複製文件 copy cp
移動文件 move mv
文件改名 ren mv
刪除文件 del rm

使用命令行編譯/調試

命令行編譯

手動編譯

在命令行下輸入 g++ a.cpp 就可以編譯 a.cpp 這個文件了(Windows 系統需提前把編譯器所在目錄加入到 PATH 中)。

編譯過程中可以加入一些編譯選項:

  • -o <文件名>:指定編譯器輸出可執行文件的文件名。
  • -g:在編譯時添加調試信息(使用 gdb 調試時需要)。
  • -Wall:顯示所有編譯警告信息。
  • -O1-O2-O3:對編譯的程序進行優化,數字越大表示採用的優化手段越多(開啓優化會影響使用 gdb 調試)。
  • -DDEBUG:在編譯時定義 DEBUG 符號(符號可以隨意更換,例如 -DONLINE_JUDGE 定義了 ONLINE_JUDGE 符號)。
  • -UDEBUG:在編譯時取消定義 DEBUG 符號。
  • -lm-lgmp: 鏈接某個庫(此處是 math 和 gmp,具體使用的名字需查閲庫文檔,但一般與庫名相同)。
Note

在 Unix 下,如使用了標準 C 庫裏的 math 庫(math.h),則需在編譯時添加 -lm 參數。2

使用 GNU Make 的內置規則9

對於名為 qwq.c/cpp/p 的 C,C++,Pascal 程序源代碼,可以使用 make qwq 自動編譯成對應名為 qwq 的程序。

如需添加額外的編譯選項,請使用 export CFLAGS="xxx" 或者 export CPPFLAGS="xxx" 定義。

Sanitizers

介紹

sanitizers 是一種集成於編譯器中,用於調試 C/C++ 代碼的工具,通過在編譯過程中插入檢查代碼來檢查代碼運行時出現的內存訪問越界、未定義行為等錯誤。

它分為以下幾種:

  • AddressSanitizer5:檢測對堆、棧、全局變量的越界訪問,無效的釋放內存、內存泄漏(實驗性)。
  • ThreadSanitizer6:檢測多線程的數據競爭。
  • MemorySanitizer7:檢測對未初始化內存的讀取。
  • UndefinedBehaviorSanitizer8:檢測未定義行為。

使用方式

最新版本的 clang++g++ 以及 MSVC(部分支持)均已內置 sanitizers,但功能和使用方法有所不同,這裏以 clang++ 為例,它的使用方法如下:

1
$ clang++ -fsanitize=<name> test.cc

其中 <name> 即為要啓用的功能(一個 sanitizer 可理解為一些功能的集合),例如:

1
2
$ clang++ -fsanitize=memory test.cc # 啓用 MemorySanitizer
$ clang++ -fsanitize=signed-integer-overflow test.cc # 啓用有符號整型溢出檢測

之後直接像平常一樣運行可執行文件即可,如果 sanitizer 檢測到錯誤,則會輸出到 stderr 流,例如:

1
2
$ ./a.out
test.cc:3:5: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'

時間/內存代價

顯而易見,這些調試工具會嚴重拖慢代碼的運行時間和增大所用內存,以下為使用它們的時間/內存代價:

名稱 所增大內存倍數 所增大時間倍數
AddressSanitizer N/A 2
ThreadSanitizer 5~15 5~10
MemorySanitizer N/A 3
UndefinedBehaviorSanitizer N/A N/A

命令行調試

在命令行下,最常用的調試工具是 gdb。

執行 gdb a 就可以調試 a 程序。

以下是幾個 gdb 調試的常用命令(大多數命令可以縮寫,用命令開頭的若干個字母就可以代表該命令):

  • listl):列出程序源代碼,如 l main 指定列出 main 函數附近的若干行代碼。
  • breakb):設置斷點,如 b main 表示在 main 函數處設置斷點。
  • runr):運行程序直到程序結束運行或遇到斷點。
  • continuec):在程序遇到斷點後繼續執行,直到程序結束運行或到達下一個斷點。
  • nextn):執行當前行語句,如果當前行有函數調用,則將其視為一個整體執行。
  • steps):執行當前行語句,如果當前行有函數調用,則進入該函數內部。
  • finishfin):繼續執行至當前函數返回。
  • call:調用某個函數,例如:call f(2)(以參數 2 調用函數 f)。
  • quitq):退出 gdb。
  • displaydisp):指定程序暫停時顯示的表達式。
  • printp):打印表達式的值。

    displayprint 指令都支持控制輸出格式,其方法是在命令後緊跟 / 與格式字符,例如 p/d test(按照十進制打印變量 test 的值), 支持的格式字符有:

格式字符 對應格式
d 按十進制格式顯示變量
x 按十六進制格式顯示變量
a 按十六進制格式顯示變量
t 按二進制格式顯示變量
c 按字符格式顯示變量
f 按浮點數格式顯示變量
u 按十進制格式顯示無符號整型
o 按八進制格式顯示變量

命令行使用技巧

自動補全

補全是 Shell 提供的基本功能之一,主要用於減少命令行使用中的輸入量和 typo 概率。

一般情況下,使用補全的快捷鍵一般是Tab,按下後 Shell 會根據已輸入的字符補全信息。

不同的 Shell 提供了能力不盡相同的補全能力。

以下是常見 Shell 的補全能力3

Shell 補全能力(補全範圍)
cmd(Windows 的傳統控制枱) 文件路徑
PowerShell 文件路徑、PATH 中的命令名、內建命令名、函數名、命令參數,支持模糊匹配,自動糾錯
Bash 文件路徑、PATH 中的命令名、內建命令名、函數名、命令參數
Zsh 文件路徑、PATH 中的命令名、內建命令名、函數名、命令參數,支持模糊匹配,自動糾錯和建議
Fish 文件路徑、PATH 中的命令名、內建命令名、函數名、命令參數,支持模糊匹配,補全時可顯示參數功能,自動糾錯和建議
Note

PowerShell 的部分功能需要 PSReadline Module 載入或者位於 PowerShell ISE 中。
Bash 的補全功能一般需要一個名為 bash-completions 的包才能獲得完整功能,部分軟件的補全文件由軟件包自帶。
Zsh 完整的補全功能需要配合用户預定義的文件(一般隨 Zsh 包或對應軟件包安裝)。
Fish 在默認配置下提供良好完整的補全功能,但仍有部分官方未覆蓋到的軟件的補全文件由軟件自行提供。

幫助文檔

一般來説,命令行下的程序都附有「幫助」,Windows 下一般使用 command /? 或者 command -? 獲取,Unix-like(例如 Linux)上一般使用 command --help 或者 command -h 獲取(但是 BSD 下的「幫助」往往過分簡略而難以使用)。

此外,在 Unix-like 系統上,還有可通過 man command 獲取的「手冊」(manual),相比「幫助」一般更為詳細。

built-in time 和 GNU time

測試程序運行時間時,我們通常可以使用 time 命令。

但是這個命令實際上在系統中有兩個對應的命令:一個是部分 Shell(例如 Bash)內建的命令,一個是 GNU time(是一個單獨的軟件)。這兩個之間存在一些差異。

一般在 Bash 中直接使用 time 調用的是 Bash 內建的版本,我們可以使用 TIMEFORMAT 環境變量控制其輸出格式,例如將其設為 %3lR 即可輸出三位精度的實際運行時間,%3lU 即可輸出三位精度的用户空間運行時間。4

如果想要調用 GNU 版本的 time,則需使用 \time 或者 /usr/bin/time 調用,但是它的輸出格式並不易讀,我們可以附加 -p 參數(即為 \time -p)來獲得易讀的輸出。

管道

假設我們現在有兩個程序 A 和 B,都用標準輸入輸出,如何讓 A 的輸出重定向到 B 的輸入?

我們可以使用上文中提到的重定向的方式,先把 A 的輸出重定向到一個臨時文件,在把 B 的輸入重定向到這個臨時文件上。

但這個方法很低效,不僅需要創建新的文件,磁盤 IO 的操作也可能成為瓶頸,而且兩個程序不能同時運行,必須等 A 跑完了才能開始跑 B。有沒有更好的方法?

有,那就是 管道,使用起來也非常簡單,如下操作即可:

1
$ A | B

這會在內存創建一個管道,然後兩個程序被同時啓動。程序 A 每次要輸出被重定向到這個管道中,而這個管道本身不會存儲數據(其實有一個很小的緩衝區)。在 B 讀取之前,A 的輸出操作會被阻塞,等到 B 把數據讀入以後,A 的輸出才能繼續進行。這樣優美地解決了上述的問題,沒有磁盤 IO 操作,兩份代碼同時運行,也沒有額外消耗很多的內存儲存中間結果。

命名管道

有時候我們不只是要把一個程序的輸出重定向到另一個的輸入。比如在做 IO 交互題的時候,經常需要將 A 的輸出重定向到 B 的輸入,B 的輸出重定向到 A 的輸出,這個時候用上文提到的普通管道就無能為力了。而重定向到文件,有無法讓兩個程序同時運行。這個時候就需要一個長得像文件的管道——命名管道。

在 Unix 系統中,可以使用如下命令創建命名管道(以命名為 my_pipe 舉例):

1
$ mkfifo my_pipe

這個時候使用 ls 命令列出當前目錄下的文件,會發現多了一個 my_pipe| 的文件。這就創建了一個命名管道,文件名後的 | 代表這是一個管道文件。然後就可以像文件的重定向一樣向這個管道中讀寫了。

通過命名管道,我們可以通過這樣的方式讓兩個程序交互:

1
2
3
$ mkfifo input output
$ ./checker > input < output # 這裏一定要把 > input 寫在前面,不然 shell 會先打開 output 管道,而這個管道現在並沒有東西,會阻塞 checker 的運行。
$ ./my_code < input > output

使用完後,可以像普通文件一樣用 rm 命令刪除命名管道。

參考資料與註釋