自定義命令行解釋器shell

目錄

一、模塊框架圖

二、實現目標

三、實現原理

四、全局變量?

五、環境變量函數?

六、初始化環境變量表函數

七、輸出命令行提示符模塊

八、提取命令輸入模塊

九、填充命令行參數表模塊

十、檢測并處理內建命令模塊

十一、執行命令模塊

十二、源碼


一、模塊框架圖

二、實現目標

  • 要能處理普通命令
  • 要能處理內建命令
  • 要能幫助我們理解內建命令/本地變量/環境變量這些概念
  • 要能幫助我們理解shell的允許原理

三、實現原理

#?考慮下面這個與shell典型的互動:

[root@localhost epoll]# lsclient.cpp  readme.md  server.cpp  utility.h[root@localhost epoll]# psPID TTY          
TIME CMD3451 pts/0    
3514 pts/0    
00:00:00 bash00:00:00 ps

# ?下圖的時間軸來表?事件的發?次序。其中時間從左向右。shell由標識為sh的?塊代表,它隨著時間的流逝從左向右移動。shell從??讀?字符串"ls"。shell建??個新的進程,然后在那個進程中運?ls程序并等待那個進程結束。

#?然后shell讀取新的??輸?,建??個新的進程,在這個進程中運?程序 并等待這個進程結束。

四、全局變量?

  • 我們的shell內部有兩張表命令行參數表和環境變量表
  • 同時我們還要定義一張哈希表方便處理別名
  • 定義兩個數組用來方便處理記錄路徑
  • lastcode記錄上一次的進程退出碼、
  • 宏定義大小方便開辟數組 以及命令行輸出格式
#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "// 下面是shell定義的全局數據// 1. 命令行參數表
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;// 2. 環境變量表
#define MAX_ENVS 100
char* g_env[MAX_ENVS];
int g_envs = 0;// 3. 別名映射表
std::unordered_map<std::string, std::string> alias_list;// for test
char cwd[1024];
char cwdenv[1024];// last exit code
int lastcode = 0;

五、環境變量函數?

  • ?環境標量函數直接調用getenv獲取在返回即可
  • 注意GetPWD需要ssnprintf格式化寫入即可
  • DirName直接從后面查找分割符\ 然后返回之后的字符串即可
const char* GetUserName()
{const char* name = getenv("USER");return name == NULL ? "None" : name;
}const char* GetHostName()
{const char* hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
}const char* GetPwd()
{//const char *pwd = getenv("PWD");const char* pwd = getcwd(cwd, sizeof(cwd));if (pwd != NULL){snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "" : home;
}
string DirName(const char* pwd)
{
#define SEP "/"string ret = pwd;int index = ret.rfind(SEP);if (ret == SEP){return SEP;}if (index == string::npos){return "BUG?";}string t = ret.substr(index+1);return t;
}

六、初始化環境變量表函數

  • 直接把enriron指向的環境變量表拷貝到我們的環境變量表里面 為了區分我們的shell和系統的我們在末尾添加上haha區分
  • 再讓environ指向我們的環境變量表即可。
void InitEnv()
{extern char** environ;memset(g_env, 0, sizeof(g_env));g_envs = 0;for (int i = 0; environ[i]; i++){g_env[i] = (char*)malloc(strlen(environ[i]) + 1);strcpy(g_env[i], environ[i]);g_envs++;}g_env[g_envs++] = "haha";g_env[g_envs] = NULL;for (int i = 0; g_env[i]; i++){putenv(g_env[i]);cout << g_env[i] << endl;}environ = g_env;
}

七、輸出命令行提示符模塊

  • 先定義一個字符輸出存儲命令行提示符
  • 然后snprintf格式化寫入字符數組中 在輸出即可。
void PrintCommandPrompt()
{char cmd_prompt[COMMAND_SIZE];MakeCommandLine(cmd_prompt, sizeof(cmd_prompt));std::cout << cmd_prompt;
}
void MakeCommandLine(char cmd_prompt[], int size)
{snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
}
const char* GetUserName()
{const char* name = getenv("USER");return name == NULL ? "None" : name;
}const char* GetHostName()
{const char* hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
}const char* GetPwd()
{//const char *pwd = getenv("PWD");const char* pwd = getcwd(cwd, sizeof(cwd));if (pwd != NULL){snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "" : home;
}
string DirName(const char* pwd)
{
#define SEP "/"string ret = pwd;int index = ret.rfind(SEP);if (ret == SEP){return SEP;}if (index == string::npos){return "BUG?";}string t = ret.substr(index+1);return t;
}

八、提取命令輸入模塊

  • 這里先fgets獲取標準輸入到字符數組中
  • 然后構造字符串刪除erase\0 然后去map中判斷是否為別名
  • 如果是直接把value的值拷貝到數組中即可 然后return ture退出
  • 不是別名則把用戶回車的\n字符消除 同時如果用戶只回車此時n==0
  • 不做處理返回false 其他返回true;
bool GetCommandLine(char* commandline, int size)
{const char* ret=fgets(commandline, size, stdin);if (ret == NULL){return false;}std::string a = ret;a.erase(a.size() - 1, 1);if (alias_list.count(a)){strcpy(commandline, alias_list[a].c_str());return true;}int n = strlen(commandline);commandline[n - 1] = 0;if (n == 0){return false;}return true;
}

九、填充命令行參數表模塊

  • 先strtok獲取指向第一個空格字符串
  • while的那個ret不為空時填充g_argv參數表
  • 繼續分割填充,直到找不到空格,說明字符串分割完畢
  • 最后填充在g_argv表最后填充NULL即可
  • 根據g_argv大小判斷是否填充成功 成功返回ture 反之返回flase?
bool CommandParse(char* commandline)
{
#define SEP " "g_argc = 0;char* ret = strtok(commandline, SEP);if (ret == NULL){return false;}while (ret){g_argv[g_argc++] = ret;ret = strtok(nullptr, SEP);}g_argv[g_argc] = ret;for (int i = 0; g_argv[i]; i++){cout << g_argv[i] << " ";}cout << endl;return g_argc > 0 ? true : false;
}

十、檢測并處理內建命令模塊

#?先根據g_argv表的第一個命令 判斷分流檢測處理

  • cd命令如果只有cd 那就直接獲取家目錄的環境變量字符串
  • chdir修改當前命令為家目錄即可
  • 否則直接chdir修改當前目錄的路徑為g_argv[1]
  • echo命令判斷分流
  • echo $?直接返回lastcode退出碼 再設置wield0即可
  • echo $xxx 直接獲取xxx的環境變量 再輸出即可
  • echo xxx 直接打印xxx字符串g_argv[1]即可
  • export命令先判斷是否填寫了要導入的環境變量
  • 沒有直接返回ture不做處理 否則直接putenv導入g_argv[1]環境變量即可
  • alias命令 這里只處理不帶命令選項的替換
  • strtok分割=前后字符串然后 存儲到map中即可

bool CheckAndExecBuiltion()
{std::string t = g_argv[0];if (t == "cd"){return Cd();}else if (t == "echo"){return Echo();}else if (t == "export"){return Export();}else if (t == "alias"){cout << "開始替換" << endl;return Alias();}else{return false;}
}
bool Cd()
{std::string t;if (g_argc == 1){t = GetHome();if (t == ""){return true;}chdir(t.c_str());}else{t =g_argv[1];chdir(t.c_str());}return true;
}
bool Echo()
{std::string t = g_argv[1];if (t=="$?"){std::cout << lastcode << std::endl;lastcode = 0;}else if (t[0] == '$'){char* ret = getenv(t.substr(1).c_str());if (ret){cout << ret << endl;}}else{cout << t << endl;}return true;
}
bool Export()
{if (g_argc != 2){return true;}putenv(g_argv[1]);return true;
}
bool Alias()
{
#define SEP "="char* t = g_argv[1];char* ret = strtok(t, SEP);if (ret == NULL){return false;}string a=std::string(ret),b = std::string(strtok(NULL, SEP));alias_list[a] = b;cout << alias_list[a] <<" "<<alias_list.count(a)<<endl;cout << a<< "->" << b << endl;return true;
}

十一、執行命令模塊

  • 直接創建子進程,子進程通過execvp程序替換執行命令,執行完后exit退出
  • 然后父進程waitpid等待子進程,同時把lastcode更新即可
int Execute()
{pid_t id = fork();if (id == 0){execvp(g_argv[0], g_argv);exit(1);}int status = 0;pid_t rid=waitpid(id, &status, 0);if (rid > 0){lastcode = WEXITSTATUS(status);}return 0;
}

    十二、源碼

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/wait.h>
    #include<unordered_map>#define COMMAND_SIZE 1024
    #define FORMAT "[%s@%s:%s]# "// 下面是shell定義的全局數據//1. 命令行參數表
    #define MAXARGC 128
    char *g_argv[MAXARGC]; 
    int g_argc = 0;//2. 環境變量表
    #define MAX_ENVS 100
    char* g_env[MAX_ENVS];
    int g_envs = 0;//3. 別名映射表
    std::unordered_map<std::string, std::string> alias_list;// for test
    char cwd[1024];
    char cwdenv[1024];// last exit code
    int lastcode = 0;//獲取用戶名
    const char *GetUserName()
    {const char *name = getenv("USER");return name == NULL ? "None" : name;
    }//獲取主機名
    const char *GetHostName()
    {const char *hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
    }//獲取當前路徑
    const char *GetPwd()
    {//const char *pwd = getenv("PWD");const char *pwd = getcwd(cwd, sizeof(cwd));if(pwd != NULL){snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
    }//獲取家目錄
    const char *GetHome()
    {const char *home = getenv("HOME");return home == NULL ? "" : home;
    }//初始化環境變量
    void InitEnv()
    {extern char **environ;memset(g_env, 0, sizeof(g_env));g_envs = 0;//本來要從配置文件中來//1. 獲取環境變量for (int i = 0; environ[i]; i++){// 1.1 申請空間g_env[i] = (char*)malloc(strlen(environ[i]) + 1);strcpy(g_env[i], environ[i]);g_envs++;}g_env[g_envs++] = (char*)"HAHA=for_test"; //for_testg_env[g_envs] = NULL;//2. 導成環境變量for (int i = 0; g_env[i]; i++){putenv(g_env[i]);std::cout << g_env[i] << std::endl;}environ = g_env;
    }//command
    bool Cd()
    {if(g_argc == 1){std::string home = GetHome();if(home.empty()) return true;chdir(home.c_str());}else{std::string where = g_argv[1];// cd - / cd ~if(where == "-"){// Todo}else if(where == "~"){// Todo}else{chdir(where.c_str());}}return true;
    }void Echo()
    {if(g_argc == 2){// echo "hello tata"// echo $?// echo $PATHstd::string opt = g_argv[1];if(opt == "$?"){std::cout << lastcode << std::endl;lastcode = 0;}else if(opt[0] == '$'){std::string env_name = opt.substr(1);const char *env_value = getenv(env_name.c_str());if(env_value)std::cout << env_value << std::endl;			}else{std::cout << opt << std::endl;}}
    }bool Export()
    {if (g_argc != 2){return false;}putenv(g_argv[1]);return true;
    }bool Alias()
    {char* t = g_argv[1];char* ret = strtok(t, "=");if (ret == NULL){return false;}string a=std::string(ret),b = std::string(strtok(NULL, "="));alias_list[a] = b;std::cout << alias_list[a] << " " << alias_list.count(a) << std::endl;std::cout << a << "->" << b << std::endl;return true;
    }//絕對路徑改為目錄名
    std::string Dirname(const char *pwd)
    {
    #define SLASH "/"std::string dir = pwd;if(dir == SLASH) return SLASH;auto pos = dir.rfind(SLASH);if(pos == std::string::npos) return "BUG?";return dir.substr(pos+1); // +1是為了去掉目錄名前面的/
    }//制作命令行提示符
    void MakeCommandLine(char cmd_prompt[], int size)
    {snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), Dirname(GetPwd()).c_str());
    }//打印命令行提示符
    void PrintCommand()
    {char prompt[COMMAND_SIZE];MakeCommandLine(prompt, sizeof(prompt));printf("%s", prompt);fflush(stdout);
    }//判斷輸入內容
    bool GetCommandLine(char *out, int size)
    {//scanf()是以空格作為分隔符的,但是我們的命令是一整個字符串//ls -a -l => "ls -a -l\n”字符串char *c = fgets(out, size, stdin);if(c == NULL) return false;out[strlen(out)-1] = 0; // 清理\nif(strlen(out) == 0) return false;return true;
    }//命令行分析 "ls -a -l" => "ls" "-a" "-l"
    bool CommandParse(char *commandline)
    {
    #define SEP " " //分隔符g_argc = 0;g_argv[g_argc++] = strtok(commandline, SEP);while((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));g_argc--; // NULL不算,去掉return g_argc > 0 ? true:false;
    }//測試命令行參數分割,打印
    void PrintArgv()
    {for(int i = 0; g_argv[i]; i++){printf("argv[%d]->%s\n", i, g_argv[i]);}printf("g_argc:%d\n", g_argc);
    }//處理并執行內建命令
    bool CheckAndExcuteBuiltn()
    {std::string cmd = g_argv[0];if(cmd == "cd"){Cd();return true;}else if(cmd == "echo"){Echo();return true;}else if(cmd == "export"){Export();return true;}else if(cmd == "alias"){std::cout << "開始替換" << std::endl;return Alias();}elsereturn false;
    }//執行命令
    int Execute()
    {pid_t id = fork();if(id == 0){//childexecvp(g_argv[0], g_argv);exit(1);}int status = 0;//fathepid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);}return 0;
    }int main()
    {// shell啟動的時候需要從系統中獲取環境變量// 我們的環境變量信息應該從父shell統一來InitEnv();while(true)//持續運行{// 1. 輸出命令行提示符PrintCommand();// 2. 獲取用戶輸入的命令char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline, sizeof(commandline)))continue;//什么都沒輸入直接回車,繼續運行printf("echo %s\n", commandline);// 3. 命令行分析 "ls -a -l" => "ls" "-a" "-l"if(!CommandParse(commandline))continue;//PrintArgv(); //測試// 4. 檢測并處理內建命令if(CheckAndExcuteBuiltn())continue;// 5. 執行命令Execute();}// ClearUp();return 0;
    }

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/diannao/92144.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/diannao/92144.shtml
    英文地址,請注明出處:http://en.pswp.cn/diannao/92144.shtml

    如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

    相關文章

    uniapp使用uni-ui怎么修改默認的css樣式比如多選框及樣式覆蓋小程序/安卓/ios兼容問題

    修改 uni-ui 多選框 (uni-data-checkbox) 的默認樣式 在 uniapp 中使用 uni-ui 的 uni-data-checkbox 組件時&#xff0c;可以通過以下幾種方式修改其默認樣式&#xff1a; 方法一&#xff1a;使用深度選擇器格式一&#xff1a;在頁面的 style 部分使用深度選擇器 >>>…

    《Linux 環境下 Nginx 多站點綜合實踐:域名解析、訪問控制與 HTTPS 加密部署》?

    綜合練習:請給openlab搭建web網站&#xff0c;網站需求&#xff1a; 1.基于域名www.openlab.com可以訪問網站內容為 welcome to openlab!!&#xff0c; 2.給該公司創建三個子界面分別顯示學生信息&#xff0c;教學資料和繳費網站&#xff0c;基于www.openlab.com/student 網站訪…

    網絡基礎1-11綜合實驗(eNSP):vlan/DHCP/Web/HTTP/動態PAT/靜態NAT

    注&#xff1a;在華為模擬器&#xff08;eNSP&#xff09;上做的實驗其中&#xff0c;在內網實驗&#xff1a;Vlan/DHCP/VWeb/HTTP&#xff0c;在外網實驗&#xff1a;動態PAT/靜態NAT一、拓撲結構1. 核心設備與連接設備接口連接對象VLAN/IP角色LSW2/LSW3Ethernet 0/0/1-2PC1/P…

    Mac上安裝Claude Code的步驟

    以下是基于現有信息的簡明安裝指南&#xff0c;適用于macOS系統。請按照以下步驟操作&#xff1a; 前提條件 操作系統&#xff1a;macOS 10.15或更高版本。Node.js和npm&#xff1a;Claude Code基于Node.js&#xff0c;需安裝Node.js 18和npm。請檢查是否已安裝&#xff1a; …

    MybatisPlus-15.擴展功能-邏輯刪除

    一.邏輯刪除配置邏輯刪除的字段時&#xff0c;logic-delete-field字段配置的是邏輯刪除的實體字段名。字段類型可以是boolean和integer。在java中默認是boolean類型。邏輯已刪除值默認為1&#xff0c;而邏輯未刪除值默認為0。當是1時代表已刪除(1在數據庫表中為true&#xff0c…

    IDEA 同時修改某個區域內所有相同變量名

    在 IntelliJ IDEA 中&#xff0c;同時修改某個區域內所有 相同變量名 的快捷鍵是&#xff1a; ? Shift F6&#xff08;重命名變量&#xff09; 但這個快捷鍵默認是 全局重命名&#xff0c;如果你想 僅修改某個方法或代碼塊內的變量名&#xff0c;可以這樣做&#xff1a;&…

    Telink BLE 低功耗學習

    低功耗管理&#xff08;Low Power Management&#xff09;也可以稱為功耗管理&#xff08;Power Management&#xff09;&#xff0c;本?檔中會簡稱為PM。Telink低功耗解惑我查閱多連接SDK開發手冊時&#xff0c;低功耗管理章節看了兩三遍也沒太明白&#xff0c;有以下幾個問題…

    設備管理系統(MMS)如何在工廠MOM功能設計和系統落地

    一、核心系統功能模塊設備管理系統圍繞設備全生命周期管理設計&#xff0c;涵蓋基礎數據管理、設備運維全流程管控及統計分析功能&#xff0c;具體如下&#xff1a;基礎數據管理設備與備件臺賬&#xff1a;包含設備臺賬&#xff08;設備編號、識別碼、型號、生產日期等&#xf…

    低空經濟展 | 牧羽天航空攜飛行重卡AT1300亮相2025深圳eVTOL展

    為深入推動低空經濟產業高質量發展&#xff0c;構建全球eVTOL&#xff08;電動垂直起降飛行器&#xff09;產業交流合作高端平臺&#xff0c;2025深圳eVTOL展定于2025年9月23日至25日在深圳坪山燕子湖國際會展中心隆重舉辦。本屆展會以“低空經濟?eVTOL?航空應急救援?商載大…

    CS231n-2017 Lecture4神經網絡筆記

    神經網絡&#xff1a;我們之前的線性分類器可以接受輸入&#xff0c;進而給出評分&#xff0c;這是一種線性變換&#xff0c;再此基礎上&#xff0c;我們對這種線性變換結果進行非線性變換&#xff0c;并輸入到下一層線性分類器中&#xff0c;這個過程就像是人類大腦神經的運作…

    暑期算法訓練.5

    目錄 20. 力扣 34.在排序數組中查找元素的第一個位置和最后一個位置 20.1 題目解析&#xff1a; 20.2 算法思路&#xff1a; 20.3 代碼演示&#xff1a; ?編輯 20.4 總結反思&#xff1a; 21.力扣 69.x的平方根 21.1 題目解析&#xff1a; 21.2 算法思路&#xff1a;…

    【HDLBits習題詳解 2】Circuit - Sequential Logic(5)Finite State Machines 更新中...

    1. Fsm1&#xff08;Simple FSM 1 - asynchronous reset&#xff09;狀態機可分為兩類&#xff1a;&#xff08;1&#xff09;Mealy狀態機&#xff1a;輸出由當前狀態和輸入共同決定。輸入變化可能立即改變輸出。&#xff08;2&#xff09;Moore狀態機&#xff1a;輸出僅由當前…

    多級緩存(億級流量緩存)

    傳統緩存方案問題 多級緩存方案 流程 1.客戶端瀏覽器緩存頁面靜態資源; 2. 客戶端請求到Nginx反向代理;[一級緩存_瀏覽器緩存] 3.Nginx反向代理將請求分發到Nginx集群(OpenResty); 4.先重Nginx集群OpenResty中獲取Nginx本地緩存數據;[二級緩存_Nginx本地緩存] 5.若Nginx本地緩存…

    淺談Rust語言特性

    如大家所了解的&#xff0c;Rust是一種由Mozilla開發的系統編程語言&#xff0c;專注于內存安全、并發性和高性能&#xff0c;旨在替代C/C等傳統系統編程語言。Rust 有著非常優秀的特性&#xff0c;例如&#xff1a;可重用模塊 內存安全和保證&#xff08;安全的操作與不安全的…

    React探索高性能Tree樹組件實現——react-window、react-vtree

    &#x1f680; 簡介 在現代 Web 應用中&#xff0c;處理大量層級數據的樹形結構是一個常見挑戰。傳統的樹組件在面對成千上萬個節點時往往會出現性能瓶頸&#xff0c;導致頁面卡頓、內存占用過高等問題。本文將深入探討如何使用 react-window 和 react-vtree 構建高性能的虛擬…

    C++ 中的默認構造函數:非必要,不提供

    《More Effective C&#xff1a;35個改善編程與設計的有效方法》 讀書筆記&#xff1a;非必要不提供default constructor在 C 中&#xff0c;默認構造函數&#xff08;即無需任何參數即可調用的構造函數&#xff09;是對象“無中生有”的一種方式。它的核心作用是在沒有外部信息…

    如何選擇低代碼開發平臺

    選擇低代碼開發平臺需要考慮平臺的開發效率、靈活性和擴展能力、安全性和合規性、成本效益等關鍵因素。 具體來說&#xff0c;平臺的靈活性和擴展能力尤為重要&#xff0c;這決定了平臺是否能長期滿足企業日益增長的復雜需求。例如&#xff0c;企業在評估平臺時&#xff0c;應關…

    電子數據取證領域的雙輪驅動——手工分析 vs 自動化分析

    在你剛步入電子數據取證領域時&#xff0c;可能很快就注意到一個普遍現象&#xff1a;大多數取證分析師前期都花費大量時間在網上查閱博客、PDF、推文等信息&#xff0c;尋找證據線索的“藏身之處”——例如注冊表項、日志文件路徑、可疑文件命名模式或遠程登錄痕跡等。這種信息…

    《Python 實時通信全解:掌握 WebSocket 技術與 HTTP 的本質區別》

    ??《Python 實時通信全解:掌握 WebSocket 技術與 HTTP 的本質區別》 引言:通信方式的演進與 Python 的角色 在數字化世界里,**“實時性”**已經成為構建高質量應用的核心訴求。從聊天工具到股票交易系統,再到物聯網設備管理——通信的即時響應能力直接決定用戶體驗。而…

    GeoTools 自定義坐標系

    前言在GIS開發中&#xff0c;坐標系統是重中之重&#xff0c;在接到任務時首先要確定的就是坐標系。大多數地圖庫或者互聯網地圖默認支持WGS84地理坐標系和Web墨卡托投影坐標系。而在我國要求使用自然資源數據使用2000國家大地坐標&#xff08;CGCS2000&#xff09;。1. 背景 經…