【Linux實踐系列】:用c語言實現一個shell外殼程序

🔥本文專欄:Linux Linux實踐項目
🌸博主主頁:努力努力再努力wz

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

那么今天我們就要進入Linux的實踐環節,那么我們之前學習了進程控制相關的幾個知識點,比如進程的終止以及進程的等待和進程的替換,那么我們接下來就要結合前面所講的進程控制相關的接口比如fork以及waitpid和execl等來自己實現一個命令行解釋器,那么廢話不多說,讓我們進入正文

★★★ 本文前置知識
進程的替換
進程的終止與等待
進程的概念


shell實現的框架

那么在用c語言真正上手實操我們的shell外殼程序時,那么我們腦海里得有一個大體的實現框架,也就是所謂的一個整體思路,在有了整體思路后我們再去談具體每個模塊的細節,那么我們首先就先從shell本身的工作原理作為切入口入手

那么我們的shell也就是我們的命令行解釋器,那么它的工作就是獲取用戶輸入的指令,然后來執行用戶輸入的指令,那么我們知道用戶輸入的指令的本質上就是一個字符串,所以shell首先就得讀取到用戶輸入的字符串,然后保存在一個字符數組中,讀取到用戶輸入的字符串之后,那么緊接著下一步便是解析用戶輸入的字符串,那么我們用戶輸入的指令無非可以分成兩大部分,分別是指令部分以及參數部分,那么這里我們就需要定義一個字符指針數組,那么數組的每一個元素就是一個指針,那么指針指向的就是一個字符串,那么我們用戶輸入的字符串的指令部分就保存在字符指針數組的第一個元素也就是下標為0的位置,那么參數部分則依次保存在之后的位置,比如我們用戶輸入的指令是ls -l -a,那么此時我們就要解析為三部分,分別是是指令部分的“ls”字符串以及兩個參數部分的字符串"-l”和“-a”,將這三個字符串則是依次保存到我們的字符指針數組下標為0和1和2的位置當中

而具體的解析這三部分字符串則需要用到我們c語言的strtok函數,那么具體細節我們下文再說,那么這里我們討論的是大的框架與思路,所以我們可以專門定義一個函數來完成這個字符串解析的模塊,它的工作就是解析用戶輸入的字符串將其指令部分以及參數部分的各個字符串分別保存到字符指針數組不同位置中,并且返回命令行的個數,比如用戶輸入的是ls -l,那么將其保存在字符指針數組char* argv[]并返回的個數就是2,,而如果是pwd,將其保存在字符指針數組char* argv[]并返回的個數就是1

那么接下來解析完用戶輸入的字符串之后,那么我們就可以來執行用戶輸入的指令了,那么這里我們知道我們用戶輸入的各種指令本質上就是在特定路徑下保存的一個可執行文件,那么指令的執行本質上就是創建一個進程,那么我們shell執行這些指令就得利用fork函數來創建一個子進程,然后我們利用fork函數的返回值,將父子進程分成不同的執行流,那么在子進程的執行流代碼片段中,我們就可以利用進程的替換,那么將我們的子進程的內容替換為我們要執行指令所對應的進程的上下文,那么我們父進程的執行流代碼片段則是等待我們子進程的退出結果,那么我們就需要用waitpid函數來獲取子進程的退出碼

最后獲取完子進程的退出碼,如果子進程沒有正常終止,那么就得將情況返回給用戶,也就是將錯誤信息打印到終端,如果子進程正常終止然后下一步就是重復我們之前上文的環節,那么重復也就意味著我們實現的時候最后這些邏輯的代碼都要封裝到一個死循環當中。

那么這就是我們實現shell外殼程序的一個大框將,那么我們可以簡單將其分為幾個模塊,分別是獲取用戶輸入->解析用戶輸入->創建子進程->子進程的替換->父進程等待獲取子進程的退出情況->重復上述步驟

那么看到這些模塊,想必你一定還有一些疑問,那么接下來我就會在下文補充每個模塊的代碼實現以及注意的一些細節,和其他的模塊的補充,那么有了大框架之后,那么接下來就讓我們具體實現每一個模塊了
在這里插入圖片描述

shell各個模塊的實現

1.獲取用戶輸入

那么我們的shell首先得獲取用戶輸入的字符串,那么我們知道在c語言中,我們獲取用戶輸入的字符串常見就是使用我們的scanf函數來獲取用戶的輸入,但是scanf函數有一個缺陷就是一旦讀取到空格的時候,那么scanf便停止讀取輸入,而我們用戶在輸入字符串的時候,會手動用空格隔開指令部分與參數部分,所以我們就不能采取scanf函數來獲取輸入,所以這里我們需要用fgets函數,那么fgets函數則是將從標準輸入流中讀取用戶的輸入,遇到換行符停止,那么我們可以指定其在輸入流中讀取的字符串的長度也就是fgets的第二個參數,那么將其保存到一個臨時字符數組中,如果讀取失敗,那么fgets則會返回NULL,讀取成功fgets則會返回保存數組的地址

  • fgets

    頭文件:<string.h>

  • 功能:獲取用戶輸入的字符串,末尾自動添加\0

    :函數原型

char *fgets(char *str, int n, FILE *stream);

而我們知道用戶在輸入之前,我們終端都會顯示一個命令行提示符,會顯示我們當前登錄的用戶名以及所處的工作目錄和運行的主機名稱,所以我們在獲取用戶輸入之前,我們得先打印一個字符串也就是命令行提示符,而切記,我們的shell命令行解釋器本質也是一個進程,所以這命令行提示符的每一個信息就保存在我們當前進程的環境變量中,我們需要通過我們的系統調用接口getenv來獲取其中特定字段的環境變量,這里就需要獲取到我們的USER以及HOSTNMAE以及PWD這三個字段,那么我們只需要向getenv函數傳遞這三個字符串的指針,那么他會依次匹配各個字段的名稱所對應的字符串并返回對應的值,也就是字符串的起始地址

代碼實現:

  printf("[%s@%s %s]$",getenv("USER"),getenv("HOSTNAME"),getenv("PWD"));if(fgets(temp,sizeof(temp),stdin)==NULL){perror("fgets");continue;}

2.解析命令行

那么現在我們獲取了我們的用戶輸入的字符串之后,那么我們是將用戶輸入的字符串保存在一個臨時字符數組里,那么接下來我們就要將這個字符串給分割,將其指令部分以及參數部分的各個字符串給分割保存到我們的字符指針數組當中,那么我們這里就專門可以定義一個函數來完成字符串解析模塊,并返回命令行參數的個數,那么我們知道我們用戶輸入的字符串會手動以空格分割,那么這里我們就需要調用我們的字符串函數也就是strtok函數來分割我們的字符串按照空格作為分隔符。但是在分割之前,我們又得注意一個細節,也就是我們用戶輸入完一個字符串,那么它會敲一個回車鍵來表示輸入的結束,而回車則是對應的一個換行符\n,他會被我們的fegts給讀取到,那么意味著在我們的字符串的末尾可能會有一個回車換行符

而回車換行符并不是我們一個有效的字符信息,所以我們在解析之前得去掉這個換行符,所以我們就利用我們的strlen函數首先獲取到我們這個字符串包括空格以及換行符的總長度,那么如果我判斷用戶輸入的字符串的len-1位置處的字符處是換行符,那么我們就將len-1位置用\0來覆蓋,而\0是標記字符串結尾的標志,那么這樣我們就可以消去末尾的回車換行符,這里是其中一個關鍵的實現細節

那么第二個細節就是我們的strtok函數的使用,那么我們strtok函數第一次調用的時候要傳遞我們要分割的字符串的首元素的地址,那么strtok內部會訪問到一個靜態的全局變量,這個靜態變量是用來保存下一次分割的位置,那么我們每次調用strtok函數的時候,會從分割的起始位置處往后掃描直到遇到分隔符,然后將分隔符的位置修改為\0,然后返回該分割起始位置的指針,而我們知道\0是標記字符串的結尾,所以返回分割起始位置的指針就達到了一個分割子串的一個效果
而下一次調用strtok函數的時候,那么我們就不用傳要分割的字符串的首元素的地址,因為上文說過strtok內部能訪問到一個記錄下一次分割位置的全局變量,那么之后的調用我們只需要傳遞一個NULL即可,它內部會繼續從這個全局變量記錄的位置開始掃描到下一個分隔符,將其修改為\0,最后如果我們開始的分割的位置是\0,也就是字符串末尾,沒有更多的子串來分割時候,那么strtok就返回一個NULL


  • strtok
    頭文件:<string.h>
    -功能:分割字符串

    :函數原型

 char *strtok(char *str, const char *delim);

在這個函數中我們就定義一個int類型的argc變量來跟蹤命令行的個數,初始化為0,而我們將分割的字符串保存在對應的字符數組的下標就是argc的值,保存之后接著遞增argc,最后返回的該argc就是我們的命令行的個數

代碼實現:

int getString(char temp[],char* argv[])
{int len=strlen(temp);if(len>0&&temp[len-1]=='\n'){temp[len-1]='\0';len--;}int argc=0;char* toke=strtok(temp," ");while(toke!=NULL&&argc<length-1){argv[argc++]=toke;toke=strtok(NULL," ");}argv[argc]=NULL;return argc;
}

3.指令判斷

那么這里在我們上文介紹實現我們的shell外殼程序的框架的時候模塊的時候,其實我們故意漏了一個模塊,那就是指令的判斷,那么想必你一定會有所疑問,那么就是我們獲取解析完用戶輸入的指令之后,我們為什么還要進行指令的判斷呢?直接通通交給子進程去執行不就完了嗎,我們父進程也就是shell外殼程序的本職工作不就是獲取用戶的輸入嗎

那么這里我們就要注意的就是,我們用戶其中輸入的指令,比如cd指令,也就是更改我們進程所處的工作目錄,那么它針對的對象其實是我們的父進程也就是我們的shell外殼程序,那么如果我們把這個指令交給了子進程去完成,將子進程替換為cd指令所對應的上下文,那么子進程的執行是不會影響父進程的,那么子進程執行結束退出之后,我們shell進程所處的工作目錄沒有進行更改,那么所以我們對于有些指令,也就是針對當前父進程shell的運行環境的指令,比如cd,比如PWD指令,那么它就不能交給子進程來執行,而是得交給父進程來自己完成,那么這些指令也就是我們的內置指令

那么內置指令那么就不再是一個編寫好的可執行文件,那么它是通常是一個實現好的庫函數或者直接嵌套在我們的shell進程所對應的代碼中,所以我們自己用c語言實現的時候,那么我們就首先準備定義一個全局屬性的字符指針數組,然后該數組里面記錄了我們所有的內置命令所對應的字符串,那么當解析完用戶的指令之后,解析完保存的字符指針數組的第一個位置就是對應用戶輸入的指令部分的字符串,所以下一步我們依次匹配保存的所有內置命令對應的字符串,如果匹配成功,那么意味著是內置指令,就直接交給我們父進程執行,匹配失敗則說明該指令不是內置命令,就交給子進程來執行,那么我們匹配的過程以及內置命令的執行的過程都可以定義兩個函數來分別實現這兩個模塊,那么其中字符串的匹配就需要用到strcmp函數來實現

而所謂的內置命令,他的底層實現的時候本質其實就是依賴用c編寫的庫函數或者系統調用,比如cd內置命令,那么它就是用chdir庫函數來實現的,那么這個庫函數的作用就是能夠訪問到當前進程的環境變量中的工作目錄字段,然后修改當前所處的工作目錄,而pwd內置命令的本質其實也就是依賴getcwd庫函數,那么該庫函數會訪問到該進程中環境變量記錄當前所處也就是工作目錄的字段PWD,將其值記錄保存到一個數組當中,并且返回指向該數組的指針

  • chdir
    頭文件:<unistd.h>

    :函數原型

  int chdir(const char *path);
  • getcwd
    頭文件:<unistd.h>

    :函數原型

    char *getcwd(char *buf, size_t size);

那么這里我在實現的時候,就只判斷了cd以及pwd這兩種內置命令,那么我們可以下來直接去添加更多的內置命令,然后查詢對應實現所依賴的庫函數或者系統調用接口

代碼實現:

bool check(char* argv[])//指令的判斷
{for(int i=0;order[i]!=NULL;i++){if(strcmp(argv[0],order[i])==0)//如果該指令是內置命令就返回true{return true;}}return false;
}
void ordercomplete(int argc,char* argv[])//內置命令的執行
{if(strcmp(argv[0],"cd")==0){if(argc==2)//cd指令最多只能兩個參數,其中第二個參數就是跳轉的工作目錄{if (chdir(argv[1]) != 0) {perror("chdir");}}else{printf("error: expected argument for 'cd'\n");}}if(strcmp(argv[0],"pwd")==0){char cwd[length];  // 定義一個字符數組錯誤的來保存我們的當前所處的工作目錄if (getcwd(cwd, sizeof(cwd)) != NULL) {printf("Current working directory: %s\n", cwd);} else {perror("getcwd failed");  // 輸出錯誤信息}}
}

5.子進程執行指令

那么剩下幾個模塊的細節和實現就很簡單了,接下來這個模塊就調用fork函數來創建一個子進程,然后利用fork函數的返回值,讓父子進程有著不同的執行流,然后我們在子進程對應的執行流代碼片段中,調用進程的替換的系統接口,而這里我們調用的exec族函數,一定是不能帶有l的比如execl以及execlp等,因為我們不知道用戶輸入的命令行個數,所以不能用可變參數列表的進程替換接口,這里要注意
而我們用戶輸入的字符串都解析在了一個字符指針數組中,所以我們傳的參數肯定就是一個數組,所以這里我們選擇進程替換的函數就是execvp,那么它可以默認在環境變量的PATH中去匹配我們用戶輸入的指令所對應的可執行文件
那么我們用execvp函數來將子進程替換為指令所對應的進程的上下文,但是我們知道我們進程替換會出現調用失敗的情況,那么調用失敗的結果則是會執行進程替換接口之后的代碼,那么我們就在execvp函數后面打印一個錯誤信息并且返回一個特殊的退出碼

6.父進程的等待

那么我們父進程對應的執行流代碼片段則是等待我們子進程的退出情況,所以我們需要調用waitpid函數來獲取子進程的退出碼,那么waitpid我們的等待方式則是設置為阻塞式等待,那么它的返回值就分別對應兩種情況,要么等待成功并且獲取到子進程的退出碼,對應的返回值就是子進程的pid,而等待失敗則是返回-1,我們對于等待失敗則是要打印錯誤信息以及子進程的退出碼

完整實現

那么將我們上面的6個模塊所對應代碼融合就是我們的shell的外殼程序,那么其實我們在實現shell外殼程序的時候,其實shell的整體實現難度不大,主要考察你對shell的工作原理的理解程度和幾個系統調用接口的熟悉程度,shell實現的真正的難點其實在它各個模塊實現的細節上,很容易出錯,其中就考察我們對于一些c語言的庫函數的掌握情況,那么接下來我就給出完成的shell的c語言代碼的實現

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<stdbool.h>
#define length 1000
#define EXIT_FAIL 40
const char* order[]={"cd","pwd",NULL};int getString(char temp[],char* argv[])
{int len=strlen(temp);if(len>0&&temp[len-1]=='\n'){temp[len-1]='\0';len--;}int argc=0;char* toke=strtok(temp," ");while(toke!=NULL&&argc<length-1){argv[argc++]=toke;toke=strtok(NULL," ");}argv[argc]=NULL;return argc;
}
bool check(char* argv[])
{for(int i=0;order[i]!=NULL;i++){if(strcmp(argv[0],order[i])==0){return true;}}return false;
}
void ordercomplete(int argc,char* argv[])
{if(strcmp(argv[0],"cd")==0){if(argc==2){if (chdir(argv[1]) != 0) {perror("chdir");}}else{printf("error: expected argument for 'cd'\n");}}if(strcmp(argv[0],"pwd")==0){char cwd[length];  // 定義一個足夠大的緩沖區來存儲路徑if (getcwd(cwd, sizeof(cwd)) != NULL) {printf("Current working directory: %s\n", cwd);} else {perror("getcwd failed");  // 輸出錯誤信息}}
}
int main()
{int argc;char* argv[length];char temp[length];while(1){printf("[%s@%s %s]$",getenv("USER"),getenv("HOSTNAME"),getenv("PWD"));if(fgets(temp,sizeof(temp),stdin)==NULL){perror("fgets");continue;}argc=getString(temp,argv);if(argc==0){continue;}if(check(argv)){ordercomplete(argc,argv);continue;}int id=fork();if(id==0){execvp(argv[0],argv);perror("execvp");exit(EXIT_FAIL);}else{int status;int m=waitpid(id,&status,0);if(m<0){perror("waitpid");}else{if(WIFEXITED(status)){if(WEXITSTATUS(status)==40){printf("error\n");}}}}}return 0;
}

在Linux上的運行截圖:
在這里插入圖片描述

結語

那么這就是用c語言實現shell外殼程序的所有內容啦,那么它也是我第一個學習Linux所完成的一個小項目,那么它這個小項目的教學價值以及學習意義其實非常高,因為它不僅可以幫組你了解shell外殼程序的工作原理,更重要的是幫組你更能熟練掌握運用那幾個關于進程控制十分重要的系統調用接口其中比如fork以及waitpid等,那么我的下一篇Linux文章就正式進入文件系統啦,我會持續更新,希望大家多多關注,那么如果本篇文章對你有所幫組的話,那么還請多多三連加關注哦,你的支持就是我最大的動力!

在這里插入圖片描述

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

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

相關文章

?算法OJ?N-皇后問題 II【回溯剪枝】(C++實現)N-Queens II

?算法OJ?N-皇后問題【回溯剪枝】&#xff08;C實現&#xff09;N-Queens 問題描述 The n-queens puzzle is the problem of placing n n n queens on an n n n \times n nn chessboard such that no two queens attack each other. Given an integer n, return the num…

03.06 QT

一、使用QSlider設計一個進度條&#xff0c;并讓其通過線程自己動起來 程序代碼&#xff1a; <1> Widget.h: #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QThread> #include "mythread.h"QT_BEGIN_NAMESPACE namespace Ui {…

Spring WebFlux 中 WebSocket 使用 DataBuffer 的注意事項

以下是修改后的完整文檔&#xff0c;包含在多個多線程環境中使用 retain() 和 release() 方法的示例&#xff0c;且確保在 finally 塊中調用 release()&#xff1a; 在 Spring WebFlux 中&#xff0c;WebSocketMessage 主要用于表示 WebSocket 的消息載體&#xff0c;其中 getP…

【CSS】Tailwind CSS 與傳統 CSS:設計理念與使用場景對比

1. 開發方式 1.1 傳統 CSS 手寫 CSS&#xff1a;你需要手動編寫 CSS 規則&#xff0c;定義類名、ID 或元素選擇器&#xff0c;并為每個元素編寫樣式。 分離式開發&#xff1a;HTML 和 CSS 通常是分離的&#xff0c;HTML 中通過類名或 ID 引用 CSS 文件中的樣式。 示例&#…

2025華為OD機試真題E卷 - 螺旋數字矩陣【Java】

題目描述 疫情期間,小明隔離在家,百無聊賴,在紙上寫數字玩。他發明了一種寫法:給出數字個數 n (0 < n ≤ 999)和行數 m(0 < m ≤ 999),從左上角的 1 開始,按照順時針螺旋向內寫方式,依次寫出2,3,…,n,最終形成一個 m 行矩陣。小明對這個矩陣有些要求: 1、…

地下井室可燃氣體監測裝置:守護地下安全,防患于未“燃”!

在城市的地下&#xff0c;隱藏著無數的燃氣管道和井室&#xff0c;它們是城市基礎設施建設的重要部分&#xff0c;燃氣的使用&#xff0c;給大家的生活提供了極大的便利。在便利生活的背后&#xff0c;也存在潛在的城市安全隱患。 近年來&#xff0c;地下井室可燃氣體泄漏事故…

【使用hexo模板創建個人博客網站】

使用hexo模板創建個人博客網站 環境準備node安裝hexo安裝ssh配置 使用hexo命令搭建個人博客網站hexo命令 部署到github創建倉庫修改_config.yml文件 編寫博客主題擴展 環境準備 node安裝 進入node官網安裝node.js 使用node -v檢查是否安裝成功 安裝成功后應該出現如上界面 …

C# OPC DA獲取DCS數據(提前配置DCOM)

OPC DA配置操作手冊 配置完成后&#xff0c;訪問遠程ip&#xff0c;就能獲取到服務 C#使用Interop.OPCAutomation采集OPC DA數據&#xff0c;支持訂閱&#xff08;數據變化&#xff09;、單個讀取、單個寫入、斷線重連

發行思考:全球熱銷榜的頻繁變動

幾點雜感&#xff1a; 1、單機游戲銷量與在線人數的衰退是劇烈的&#xff0c;有明顯的周期性&#xff0c;而在線游戲則穩定很多。 如去年的某明星游戲&#xff0c;最高200多萬在線&#xff0c;如今在線人數是48名&#xff0c;3萬多。 而近期熱門的是MH&#xff0c;在線人數8…

Unity自定義區域UI滑動事件

自定義區域UI滑動事件 介紹制作1.創建一個Image2.創建腳本 總結 介紹 一提到滑動事件聯想到有太多的插件了比如EastTouchBundle&#xff0c;今天想單純通過UI去做一個滑動事件而不是基于Box2d或者Box去做滑動事件。 制作 1.創建一個Image 2.創建腳本 using UnityEngine; us…

taosd 寫入與查詢場景下壓縮解壓及加密解密的 CPU 占用分析

在當今大數據時代&#xff0c;時序數據庫的應用越來越廣泛&#xff0c;尤其是在物聯網、工業監控、金融分析等領域。TDengine 作為一款高性能的時序數據庫&#xff0c;憑借獨特的存儲架構和高效的壓縮算法&#xff0c;在存儲和查詢效率上表現出色。然而&#xff0c;隨著數據規模…

《UE5_C++多人TPS完整教程》學習筆記34 ——《P35 網絡角色(Network Role)》

本文為B站系列教學視頻 《UE5_C多人TPS完整教程》 —— 《P35 網絡角色&#xff08;Network Role&#xff09;》 的學習筆記&#xff0c;該系列教學視頻為計算機工程師、程序員、游戲開發者、作家&#xff08;Engineer, Programmer, Game Developer, Author&#xff09; Stephe…

K8s 1.27.1 實戰系列(七)Deployment

一、Deployment介紹 Deployment負責創建和更新應用程序的實例,使Pod擁有多副本,自愈,擴縮容等能力。創建Deployment后,Kubernetes Master 將應用程序實例調度到集群中的各個節點上。如果托管實例的節點關閉或被刪除,Deployment控制器會將該實例替換為群集中另一個節點上的…

Linux(Centos 7.6)命令詳解:vim

1.命令作用 vi/vim 是Linux 系統內置不可或缺的文本編輯命令&#xff0c;vim 是vi 的加強版本&#xff0c;兼容vi 的所有指令&#xff0c;不僅能編輯文本&#xff0c;而且還具有shell 程序編輯的功能&#xff0c;可以不同顏色的字體來辨別語法的正確性。 2.命令語法 usage: …

微信小程序引入vant-weapp組件教程

本章教程,介紹如何在微信小程序中引入vant-weapp。 vant-weapp文檔:https://vant-ui.github.io/vant-weapp/#/button 一、新建一個小程序 二、npm初始化 npm init三、安裝 Vant Weapp‘ npm i @vant/weapp -

C++ 作業 DAY5

作業 代碼 Widtget.h class Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);~Widget();private:Ui::Widget *ui;/************************ 起始終止坐標 ************************/QPoint end;QPoint start;QVector<QPoint> per_start_lis…

Selenium 中 ActionChains 支持的鼠標和鍵盤操作設置及最佳實踐

Selenium 中 ActionChains 支持的鼠標和鍵盤操作設置及最佳實踐 一、引言 在使用 Selenium 進行自動化測試時&#xff0c;ActionChains 類提供了強大的功能&#xff0c;用于模擬鼠標和鍵盤的各種操作。通過 ActionChains&#xff0c;可以實現復雜的用戶交互&#xff0c;如鼠標…

前端面試技術性場景題

87.場景面試之大數運算&#xff1a;超過js中number最大值的數怎么處理 在 JavaScript 中&#xff0c;Number.MAX_SAFE_INTEGER&#xff08;即 2^53 - 1&#xff0c;即 9007199254740991&#xff09;是能被安全表示的最大整數。超過此值時&#xff0c;普通的 Number 類型會出現…

【js逆向】iwencai國內某金融網站實戰

地址&#xff1a;aHR0cHM6Ly93d3cuaXdlbmNhaS5jb20vdW5pZmllZHdhcC9ob21lL2luZGV4 在搜索框中隨便輸入關鍵詞 查看請求標頭&#xff0c;請求頭中有一個特殊的 Hexin-V,它是加密過的&#xff1b;響應數據包中全是明文。搞清楚Hexin-V的值是怎么生成的&#xff0c;這個值和cooki…

ES Module 的 import 導入和 import () 動態導入

ES Module 的 import 導入和 import () 動態導入介紹 一、ES Module 簡介 ES Module 是 JavaScript 官方提供的標準化模塊系統&#xff0c;它的出現解決了長期以來 JavaScript 在模塊管理方面的混亂局面。通過 ES Module&#xff0c;開發者可以更加方便地組織和復用代碼&…