目錄
1.與環境變量有關的實驗
A.對比命令和自制程序的運行
為什么.像ls、pwd這樣的命令運行是不需要加路徑?
執行自制程序而不加路徑的方法,看看PATH環境變量
方法1:將自制程序移動到系統的搜索路徑下
方法2:臨時修改PATH環境變量
B.查看系統中所有環境變量
解釋幾個常見的環境變量
C.獲取環境變量的函數getenv()
2.環境變量
解釋全局屬性
命令行參數
a.out子進程是如何得到環境變量表的?
證明能被子進程繼承,也就證明的全局屬性
添加環境變量的命令:export
取消環境變量的命令:unset
使用第三方變量environ來訪問環境變量
3.系統變量的分類
環境變量
本地變量
set命令
證明本地變量不可被繼承
4.系統命令的分類
常規命令和內建命令
man和help的區別
5.總結獲取環境變量的幾種方法
6.拓展閱讀:自定義入口函數
7.附: C89標準文檔對main函數的描述
1.與環境變量有關的實驗
從與環境變量有關的實驗來感受環境變量
A.對比命令和自制程序的運行
1.像ls、pwd這樣的命令運行是不需要加路徑的
2.如果不配置環境變量,那么運行開發者自制程序是需要加路徑的
例如:當前目錄下有一個a.out可執行文件,那么運行時需要加路徑:
./a.out #.表示當前目錄
a.out是由以下代碼產生的:?
#include <stdio.h>
int main()
{printf("Hello World!");return 0;
}
為什么.像ls、pwd這樣的命令運行是不需要加路徑?
之前在OS4.【Linux】基本指令入門(3)文章講過:系統的命令存儲在特定的路徑下,那么bash執行指令會到特定的路徑中找
特定的路徑存儲在環境變量PATH中
?使用echo命令打印系統的環境變量:
echo $PATH #注意: $作用:說明PATH是變量名
那么在搜索命令時,會從左向右按順序查找這些路徑下的命令,如果找到了,之后的路徑就不找了?
還可以打印其他環境變量:
echo $HOME #查看當前用戶的家目錄
?
結論:
1.PATH存儲了Linux系統命令的搜索路徑
2.執行非系統命令的程序需要加路徑,因為PATH中沒有;執行系統的命令不需要帶路徑,因為PATH存儲了搜索路徑
執行自制程序而不加路徑的方法,看看PATH環境變量
方法1:將自制程序移動到系統的搜索路徑下
之前OS4.【Linux】基本指令入門(3)在文章做過,這里不再演示
方法2:臨時修改PATH環境變量
*注意:以下兩種方法都只是臨時修改PATH環境變量,重新連接服務器時PATH會恢復默認值,可以得出:?PATH是內存中的環境變量
1.直接覆蓋掉PATH:
PATH=/home/guest
(按照賦值號=的規則: PATH的新值就是/home/guest)?
會導致有些系統命令是用不了的:
例如ls命令不可用,但pwd是可用的,具體為什么pwd可用本文后面會講
這樣就不用添加路徑了:
2.對PATH添加新的路徑
PATH=$PATH:/home/guest
(要使用冒號分割符,因為冒號分割符的作用就是分開各個路徑)
這樣就不用添加路徑了:
B.查看系統中所有環境變量
使用不帶任何選項的env命令:
其中,=前面的大寫單詞指的是環境變量名,?=后面的是該環境變量的值
環境變量名 = 對應的值
解釋幾個常見的環境變量
USER
指的是當前登錄的用戶(隨su命令切換用戶而變)
LOGNAME
和USER不同的是:在某些情況下,它的值可能會保持不變,即使通過su命令切換到其他用戶
PWD
指當前處于的絕對路徑,其值會在每次執行cd命令后時更新
*注:pwd命令不是使用了PWD環境變量,而是通過getcwd函數實現的
HOME
指當前用戶的家目錄
SHELL
指當前Shell,它的值通常是/bin/bash
C.獲取環境變量的函數getenv()
看看手冊:
getenv()的作用:獲取指定的環境變量(environment variable),需要包含<stdlib.h>頭文件
函數的聲明:?char* getenv(const char *name);
注意到有兩個參數:
示例代碼:
#include <stdlib.h>
#include <stdio.h>
int main()
{printf("PATH=%s\n",getenv("PATH"));return 0;
}
運行結果:和不帶任何選項的env命令打印的結果是一樣的
2.環境變量
從上面的三個實驗可以總結出環境變量的定義:
環境變量: 是操作系統中用來指定操作系統運行環境的一些參數,是系統提供的一組 name = value 形式的變量,不同的環境變量有不同的用途,通常具有全局屬性
例如編寫C/C++代碼的時候,在鏈接的時候,有相關環境變量幫助編譯器進行查找所鏈接的動態靜態庫
解釋全局屬性
命令行參數
回顧分析命令行參數的文章:
110.【C語言】編寫命令行程序(1)
113.【C語言】編寫命令行程序(2)
114.【C語言】實戰分析命令行程序:srom轉換工具源碼分析
一般情況下,main函數用的比較多的兩種寫法:
int main()
int main(int argc, char* argv[])
但Linux操作系統為main函數提供了第3個參數:char *envp[]?
(來自GNU Program-Arguments網站)?
注:編譯器會根據情況處理main函數的三種不同寫法
envp的全稱是environment variables?pointer array,是環境變量指針數組,可以打印內容
#include <stdio.h>
int main(int argc,char* argv[],char* envp[])
{for (size_t i=0;envp[i];i++){printf("%s\n",envp[i]);}return 0;
}
提示: argv和env表的結構都一樣
運行結果:
結論:
1.程序運行需要兩張核心向量表:?char* argv[],char* envp[],指針數組的結尾元素的值為空指針 2.main函數之前還有一些函數,這些函數負責向main函數傳遞這兩張表的指針和參數的個數argc
a.out子進程是如何得到環境變量表的?
上方運行了a.out子進程,子進程打印了操作系統的所有環境變量
bash在啟動時,會從操作系統的配置文件中讀取環境變量信息,bash產生的子進程會繼承bash得到的環境變量!
證明能被子進程繼承,也就證明的全局屬性
添加環境變量的命令:export
例如添加臨時環境變量:
export MY_ENV=1
注意: 不能寫成MY_ENV=1,這樣寫是添加本地變量;變成環境變量需要加export前綴
?檢查是否成功添加:
env | grep MY_ENV
再次執行a.out,會發現MY_ENV已經被bash的子進程a.out繼承了
取消環境變量的命令:unset
使用第三方變量environ來訪問環境變量
查詢environ的含義:
extern char **environ其實是存一些環境變量字符串指針的數組,?最后一個元素的值為空指針
libc中定義的全局變量environ指向環境變量表,environ沒有包含在任何頭文件中,所以在使用時要用extern聲明
例如以下代碼:
#include <stdio.h>
#include <unistd.h>
int main()
{extern char** environ;for ( int i = 0; environ[i]; i++) {printf("%d:%s\n", i, environ[i]);}
}
運行結果:
?
3.系統變量的分類
系統變量主要分為本地變量和環境變量
環境變量
見上方內容
本地變量
創建本地變量的命令
本地變量名 = 本地變量值 #前面不要加export
?本地變量和環境變量不同,本地變量主要作用范圍是當前shell進程,不會被子進程繼承,但環境變量可以被子進程繼承
例如添加2個本地變量:
?(\是續行符,可以幫助開發者寫多行命令)
環境變量中是查不到的:
set命令
作用:?查系統中所有的變量(本地變量+環境變量)
例如查之前添加的本地變量:
證明本地變量不可被繼承
證明bash的子進程無法獲取上面提到的VAL1和VAL2的值即可
#include <stdio.h>
#include <stdlib.h>
int main()
{printf("VAL1=%s",getenv("VAL1"));printf("VAL2=%s",getenv("VAL2"));return 0;
}
運行結果:值為空?
將VAL1和VAL2用export導出,再打印:
4.系統命令的分類
常規命令和內建命令
問題:執行echo命令讓bash產生了子進程,那為什么能獲取本地變量?
顯然echo不是常規命令
Linux下有兩種命令:
1.常規命令: 通過創建子進程;來執行常規命令
2.內建命令(也稱內置命令,英文名為builtin): bash 不創建子進程,而是由自己親自執行,類似于 bash 調用了自己寫的或者系統提供的函數
例如cd命令調用了chdir()系統調用接口,是由bash自己執行的函數,cd命令的作用是讓進程改變自己的路徑
成功切換返回0,否則返回-1?
可以寫一個簡單的切換路徑的程序:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[])
{if (argc == 1){printf("Usage:cd path");return -1;}if (chdir(argv[1]))printf("%s: No such directory\n", argv[1]);elseprintf("Change path successfully\n");while (1){scanf("%s", argv[1]);if (chdir(argv[1]))printf("%s: No such directory\n", argv[1]);elseprintf("Change path successfully\n");}return 0;
}
注意:這個程序和cd命令還不太一樣,?是讓進程改變自己的路徑,不是更改bash的路徑,因此使用上方代碼切換路徑后,pwd命令打印的值是不變的
可以在cwd文件中看到:?
當然可以繼續切換看看cwd的值的變化
man和help的區別
man主要查看的是外部命令和一些系統調用和函數,例如ls、printf……
help主要查看的就是內置命令了。
提示:使用type命令可以查看命令的類型,例如
5.總結獲取環境變量的幾種方法
1.使用命令env
2.使用getenv()函數
3.直接打印char* envp[]
4.第三方變量environ獲取
6.拓展閱讀:自定義入口函數
Linux下可以通過自定義入口函數的方法來達到不從main函數(cppreference main_function)開始執行的目的
在https://www.geeksforgeeks.org/c/write-running-c-code-without-main/提到一種方法;
#include<stdio.h>
#include<stdlib.h> // entry point function
int nomain(); void _start(){ // calling entry point nomain(); exit(0);
} int nomain()
{ puts("Geeksforgeeks"); return 0;
}
編譯命令:
gcc -o nomain.out nomain.c -nostartfiles
?運行結果:
摘自stackoverflow what-is-the-use-of-start-in-c的回答:
The symbol?
_start
?is the?entry point?of your program. That is, the address of that symbol is the address jumped to on program start. Normally, the function with the name?_start
?is supplied by a file called?crt0.o
?which contains the startup code for the C runtime environment. It sets up some stuff, populates the argument array?argv
, counts how many arguments are there, and then calls?main
. After?main
?returns,?exit
?is called.If a program does not want to use the C runtime environment, it needs to supply its own code for?
_start
. For instance, the reference implementation of the Go programming language does so because they need a non-standard threading model which requires some magic with the stack. It's also useful to supply your own?_start
?when you want to write really tiny programs or programs that do unconventional things.
翻譯: _start符號是Linux下規定的函數的入口點,程序啟動時會跳轉到該符號所表示的地址
正常情況下, 由_start標識的的函數由crt0.o文件提供,它包含了 C 運行時環境的啟動代碼. 該代碼會完成一些初始化工作: 填充參數數組 argv ,統計參數個數argc,然后調用main函數. main 返回后,再調用exit函數
如果某個程序不想使用C運行時環境,就必須自行提供 _start 的實現
當你想編寫體積極小的程序,或者做一些非常規操作時,自己寫 _start 也很有用