命令四大要素&命令行中編譯運行Java程序&Java中fork子進程


命令行的歷史和流派:

  • UNIX家族
    • POSIX標准
    • macOS
    • Linux
    • Windows Subsystem for Linux
  • Windows

一、命令的四大要素

命令的組成四要素缺一不可,以下四個要素相同就可以完全地“重現”⼀個命令,你碰到的各種各樣古怪的問題,原因⼀定是四個要素之⼀。

  • 可執行程序(Executable)
  • 參數
  • 環境變量(Environment variable)
  • 工作目錄(Working directory)

1. 工作目錄

啟動命令的當前光標所在的路徑,相對路徑都是相對於這個路徑。輸入pwd 命令可以查看當前所處的工作目錄。
可以這樣理解:命令(可執行程序)本身是存在於某個目錄的,執行一個命令時需要先找到這個命令,通常根據PATH環境變量來查找可執行程序,或者直接使用該命令的絕對路徑(使用which查看),現在拿到這個工具后,不要再關心工具從哪來,要關注干活的地方在哪,而標題中【工作目錄】就是這個工具當前干活的地方。

2. 環境變量

變量又分為局部變量和全局的環境變量,環境變量是和環境強綁定的,是一種應用廣泛的傳遞配置的方式,可以使用環境變量向不同程序傳遞參數和配置,例如CLASSPATHGOPATH
查看所有的環境變量使用export

局部變量

局部變量的作用域被限定在創建它們的 shell 中。意思是子進程中不會去繼承。local 函數可以用來創建局部變量,但僅限於函數內使用。局部變量可以通過簡單的賦予它一個值或一個變量名來設置,用 declare 內置函數來設置,或者省略也可。

name=yue
echo $name
環境變量(全局變量)

環境變量又稱全局變量,以區別於局部變量,通常,環境變量應該大寫,環境變量是已經用export內置命令導出的變量。
臨時的環境變量使用export直接在命令行中聲明即可,變量在關閉shell時失效:

export NAME=yue
echo $NAME # yue

永久的(對當前用戶永久有效)是需要把export 命令寫在啟動配置文件 ~/.bash_profile中,語法同上。保存文件后如果希望在當前 shell 中立即生效,執行 source .bash_profile,否則新打開的 shell 才會生效。
無論是臨時的還是永久的環境變量,子 shell 都會繼承當前父 shell 的環境變量,但不能逆向傳遞。可以去執行bash來創建一個子 shell 做個試驗。

還可以快速傳遞一個環境變量(只對當前執行的這行命令有效):

NAME=Tony go run main.go
#之后這個環境變量就不存在了
echo $NAME # 空空如也
系統變量

如果你在 Windows 上安裝過 Java 的開發環境,一定還記得配置 PATH 系統環境變量,這樣才能需要時根據這個 PATH 中提到的路徑,找到相應的可執行程序並運行。
所以如PATH這種系統級的環境變量,比 git bash ~/.bash_profile里 bash 終端級環境變量的作用域更廣,畢竟操作系統才是爸爸。
想證明一下很簡單,先去設置系統環境變量,比如名叫JUST_TEST,然后win + R 開一個 cmd,執行 echo%JUST_TEST%,就可以看到剛才設置的變量值。

進程(Process)

是計算機程序運行的最小單位,獨占自己的內存空間和文件資源,每個進程都和一組環境變量相綁定。子進程是由父進程 fork 出來的,環境變量(全局變量)可以被子進程繼承,所有的操作系統和編程語言都支持環境變量。
例如為當前 shell 設置了環境變量XXX,然后在當前環境下進入 node 執行環境后,可以通過process.env.XXX 看到環境變量被繼承了(正如上文提到的,局部變量不會被子進程繼承)。

3. 可執行程序

什么算是可執行程序

Windows 中 exe/bat/com 文件擴展名被認為是可執行程序,通過 Path;
UNIX/Linux 中看x權限(ls -l),即可執行權限;

去哪⾥找程序?

在 Windows 中是Path 環境變量和當前目錄;
在 UNIX/Linux 中 PATH 環境變量。
可執行程序都是從 path 中尋找路徑,如果設為空字符串,會找不到。
如果當前就在可執行程序的目錄下,對於 UNIX 體系的可以通過 ./xxx 執行,.代表當前目錄。
而對於 Windows 的 cmd 是直接輸入可執行程序的名稱,至於后綴,加不加都行,會自動尋找exe/bat/com這樣的后綴。

在腳本的第⼀⾏指定解釋器(shebang)

編輯一個xxx.sh文件時,可以在 shell 腳本中第一行指定別的解釋器:

#!/usr/bin/env node
console.log(123)

表示在當前執行上下文環境中,查找 node 可執行程序來解釋當前腳本,那么當然會從 path 環境變量中查找 node 的路徑啦,這樣寫其實就等價於直接在命令行中執行 node xxx.sh

別名(alias)

~/.bash_profile 是交互式、login 方式進入 bash 運行的
~/.bashrc 是交互式 non-login 方式進入 bash 運行的
.bash_profile 在用戶每次登錄系統時被讀取,里面的所有命令都會被bash執行。
.bashrc文件會在bash shell調用另一個bash shell時讀取,也就是在shell中再鍵入bash命令啟動一個新shell時就會去讀該文件。這樣可有效分離登錄和子shell所需的環境。
一般來說都會在.bash_profile里調用.bashrc腳本以便統一配置用戶環境。

在一個 shell 中使用alias命令設置的別名,屬於局部變量,只對當前這一層 shell 環境有效,寫在~/.bash_profile中后,每次新登錄的 shell 都會讀取,但由於alias配置的別名屬於局部變量,加上創建子 shell 時不會讀取.bash_profile(除非寫在.bashrc中),所以也就不會為子 shell 設置別名:

vim ~/.bash_profile

# 寫入如下內容,保存后 source 一下立即生效

export NAME=Tony
export AGE=25

echo '你好哇~'

alias ~='cd ~'
alias cdproject='cd ~/Projects'

每當打開一個登錄終端時,都會看到你好哇~,這說明每打開一個終端,就相當於系統新 fork 了一個 bash 終端進程,繼承了系統環境變量后,還要執行啟動文件,也就是.bash_profile

Linux 文件權限

Linux 基礎——權限管理命令chmod

4. 參數

可執行程序后面所有的都是參數。UNIX 系統約定如下(Java 並沒有嚴格遵守):
約定一:-后面只能跟一個字符,但可以合並,ls -alth等價於ls -a -l -t -h
約定二:--后面跟一個單詞,ls --all等價於ls -a

參數如果有空格,會以空格分割為多個傳遞給可執行程序;
參數不加引號或" "雙引號,命令行會對參數進行變量的替換和展開;
而使用' '單引號,命令行不會做任何特殊處理,這可用來聲明參數是一個整體:

export A=123
echo wan$A.m    # wan123.m
echo "wan$A.m"  # wan123.m
echo 'wan$A.m'  # wan$A.m

如果參數中就是要包含單引號' ',那么可以再用雙引號" "包起來或者進行轉義:

echo \'I am a boy\'   # 'I am a boy'
echo "'I am a boy'"   # 'I am a boy'

二、使用命令編譯運行Java程序

Java 世界里的一切工具都只做一件事:拼接命令行

1. 編譯運行

javac Main.java # 源文件編譯成字節碼
ls # 查看編譯結果 Main.class Mian.java
java Main # 運行

Java 中:
System.getenv()查看環境變量
System.getProperty()查看系統屬性
傳遞系統屬性要以D開頭,要注意書寫位置,如果在Mian后面就成了Main的參數了,也就是第一天學 Java 就接觸到的mian方法中的String[] args參數。如傳一個名為AAA,值為123的屬性:

java -DAAA=123 Main

user.dir查看當前工作目錄
java.version查看當前 jdk 版本

2. -classpath(-cp) 參數

import junit.extensions.ActiveTestSuite;
public class Main {
        public static void main(String[] args) {
                System.out.println(ActiveTestSuite.class.getName());
        }
}

直接執行javac Main.java會報錯找不到。
因此對於引入的第三方類庫,編譯時要用-classpath 來指定 jar 包的查找路徑(假設這個 jar 包就在當前工作目錄下):

javac -cp junit-3.8.2.jar Main.java

這次成功編譯了,因為 jar 包就是個普通的 zip 文件,里面放了一堆符合類文件。一個類的全限定類名(FQCN)的包名是和文件夾一一對應的。
這個命令里,javac是 executable 可執行程序,后面全都是參數,-classpath(-cp)指定了 jar 包路徑,Main.java是即將被編譯的文件。Main.java中有一個ActiveTestSuite,這個類肯定不能從天上掉下來,要去哪兒找呢,就只能去-cp指定的地方找。

接下使用java命令來執行有個天坑,在 UNIX 環境中和 Windows 環境中是有區別的,先說在 UNIX 環境下:

java -cp junit-3.8.2.jar:. Main

以冒號:分隔路徑,. 代表同時也在當前目錄下查找,第二個java命令Main代表告訴 JVM 要從Main類啟動程序,那么Main類從哪兒找呢?只能從-cp指定的路徑找(即.所代表的當前目錄),JVM 運行Main的時候發現引用了ActiveTestSuite類,繼續從-cp指定的路徑中查找。

以上命令在 Windows 中的 git bash 里執行時有個天坑,執行會報錯。雖然看似在 git bash 中執行了命令,但是-cp后面的路徑還是要交給 Windows 版本的java可執行程序去解析的,而在 Windows 版本 classpath 的路徑分隔符是用分號;而不是冒號:,但如果只是簡單的冒號換成分號還是不行,因為 UNIX 環境中又會用分號來分割命令(bash 中執行一下mkdir testDir; cd testDir試試就知道了),所以要再加單引號' ' ,表示不對路徑參數做任何參數解析,原樣交給Java命令。

java -cp 'junit-3.8.2.jar;.' Main

三、Java中fork子進程

java-fork-process/working-directory/run.sh:

#!/usr/bin/env sh
echo "AAA is: $AAA"
ls -alth

java-fork-process/Fork.java:

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;

public class Fork {
    public static void main(String[] args) throws Exception {
        // 使用Java代碼fork一個子進程,將fork的子進程的標准輸出重定向到指定文件:工作目錄下名為output.txt的文件
        // 工作目錄是項目目錄下的working-directory目錄(可以用getWorkingDir()方法得到這個目錄對應的File對象)
        // 傳遞的命令是sh run.sh 假設working-directory目錄下存在 run.sh 腳本文件
        // 環境變量是AAA=123

        // 1.可執行程序 2.參數
        ProcessBuilder pb = new ProcessBuilder("sh", "run.sh");
        // 3.工作目錄
        pb.directory(getWorkingDir());
        // 4.環境變量
        Map<String, String> env = pb.environment();
        env.put("AAA", "123");
        env.get("AAA");
        pb.redirectOutput(getOutputFile());
        pb.start().waitFor();
    }

    private static File getWorkingDir() {
        Path projectDir = Paths.get(System.getProperty("user.dir"));
        return projectDir.resolve("working-directory").toFile();
    }

    private static File getOutputFile() {
        return new File(getWorkingDir(), "output.txt");
    }
}

參考:

  1. linux 中的局部變量、全局變量、shell 變量的總結
  2. Linux 基礎——權限管理命令chmod
  3. 《Linux 命令行大全》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM