v72.01 鸿蒙内核源码分析(Shell解析) | 应用窥伺内核的窗口 | 百篇博客分析OpenHarmony源码
子曰:“苟正其身矣,于从政乎何有?不能正其身,如正人何?” 《论语》:子路篇
百篇博客系列篇.本篇为:
v72.xx 鸿蒙内核源码分析(Shell解析篇) | 应用窥视内核的窗口
进程管理相关篇为:
- v02.06 鸿蒙内核源码分析(进程管理) | 谁在管理内核资源
- v24.03 鸿蒙内核源码分析(进程概念) | 如何更好的理解进程
- v45.05 鸿蒙内核源码分析(Fork) | 一次调用 两次返回
- v46.05 鸿蒙内核源码分析(特殊进程) | 老鼠生儿会打洞
- v47.02 鸿蒙内核源码分析(进程回收) | 临终托孤的短命娃
- v48.05 鸿蒙内核源码分析(信号生产) | 年过半百 活力十足
- v49.03 鸿蒙内核源码分析(信号消费) | 谁让CPU连续四次换栈运行
- v71.03 鸿蒙内核源码分析(Shell编辑) | 两个任务 三个阶段
- v72.01 鸿蒙内核源码分析(Shell解析) | 应用窥伺内核的窗口
系列篇从内核视角用一句话概括shell
的底层实现为:两个任务,三个阶段。其本质是独立进程,因而划到进程管理模块。每次创建shell
进程都会再创建两个任务。
- 客户端任务(ShellEntry): 负责接受来自终端(控制台)敲入的一个个字符,字符按
VT
规范组装成一句句的命令。 - 服务端任务(ShellTask): 对命令进行解析并执行,将结果输出到控制台。
而按命令生命周期可分三个阶段. - 编辑: 鸿蒙在这个部分实现了一个简单的编辑器功能,处理控制台输入的每个字符,主要包括了对控制字符 例如
<ESC>
,\t
,\b
,\n
,\r
,四个方向键0x41
~0x44
的处理。 - 解析: 对编辑后的字符串进行解析,解析出命令项和参数项,找到对应的命令项执行函数。
- 执行: 命令可通过静态和动态两种方式注册到内核,解析出具体命令后在注册表中找到对应函数回调。将结果输出到控制台。
编辑部分由客户端任务完成,后两个部分由服务端任务完成,命令全局注册由内核完成。
- 本篇主要说 服务端任务 和 解析/执行过程.
- 客户端任务 和 编辑过程 已在(Shell编辑篇)中说明,请自行翻看.
总体过程
- 第一步: 将支持的
shell
命令注册进全局链表,支持静态和动态两种方式,内容包括命令项,参数信息和回调函数. - 第二步: 由独立任务解析出用户输入的命令行,拆分出命令项和参数内容
- 第三步: 通过命令项在全局链表中遍历找到已注册的回调函数,并执行.
结构体
鸿蒙对命令的注册用了三个结构体,个人感觉前两个可以合成一个,降低代码阅读难度.
STATIC CmdModInfo g_cmdInfo;//shell 命令模块信息,上面挂了所有的命令项(ls,cd ,cp ==)
typedef struct {//命令项
CmdType cmdType; //命令类型
//CMD_TYPE_EX:不支持标准命令参数输入,会把用户填写的命令关键字屏蔽掉,例如:输入ls /ramfs,传入给注册函数的参数只有/ramfs,而ls命令关键字并不会被传入。
//CMD_TYPE_STD:支持的标准命令参数输入,所有输入的字符都会通过命令解析后被传入。
const CHAR *cmdKey; //命令关键字,例如:ls 函数在Shell中访问的名称。
UINT32 paraNum; //调用的执行函数的入参最大个数,暂不支持。
CmdCallBackFunc cmdHook;//命令执行函数地址,即命令实际执行函数。
} CmdItem;
typedef struct { //命令节点
LOS_DL_LIST list; //双向链表
CmdItem *cmd; //命令项
} CmdItemNode;
/* global info for shell module */
typedef struct {//shell 模块的全局信息
CmdItemNode cmdList; //命令项节点
UINT32 listNum;//节点数量
UINT32 initMagicFlag;//初始魔法标签 0xABABABAB
LosMux muxLock; //操作链表互斥锁
CmdVerifyTransID transIdHook;//暂不知何意
} CmdModInfo;
解读
CmdItem
为注册的内容载体结构体,cmdHook
为回调函数,是命令的真正执行体.- 通过双向链表
CmdItemNode.list
将所有命令穿起来 CmdModInfo
记录命令数量和操作的互斥锁,shell
的魔法数字为0xABABABAB
第一步 | Shell 注册
静态宏方式注册,链接时处理
静态注册命令方式一般用在系统常用命令注册,鸿蒙已支持以下命令.arp cat cd chgrp chmod chown cp cpup
date dhclient dmesg dns format free help hwi
ifconfig ipdebug kill log ls lsfd memcheck mkdir
mount netstat oom partinfo partition ping ping6 pwd
reset rm rmdir sem statfs su swtmr sync
systeminfo task telnet test tftp touch umount uname
watch writeproc
例如注册
ls
命令SHELLCMD_ENTRY(ls_shellcmd, CMD_TYPE_EX, "ls", XARGS, (CMD_CBK_FUNC)osShellCmdLs)
需在链接选项中添加链接该新增命令项参数,具体在
liteos_tables_ldflags.mk
文件的LITEOS_TABLES_LDFLAGS
项下添加-uls_shellcmd
。至于SHELLCMD_ENTRY
是如何实现的在链接阶段的注册,请自行翻看(内联汇编篇),有详细说明实现细节.动态命令方式,运行时处理
动态注册命令方式一般用在用户命令注册,具体实现代码如下:osCmdReg(CMD_TYPE_EX, "ls", XARGS, (CMD_CBK_FUNC)osShellCmdLs)
{
// ....
//5.正式创建命令,挂入链表
return OsCmdItemCreate(cmdType, cmdKey, paraNum, cmdProc);//不存在就注册命令
}
//创建一个命令项,例如 chmod
STATIC UINT32 OsCmdItemCreate(CmdType cmdType, const CHAR *cmdKey, UINT32 paraNum, CmdCallBackFunc cmdProc)
{
CmdItem *cmdItem = NULL;
CmdItemNode *cmdItemNode = NULL;
//1.构造命令节点过程
cmdItem = (CmdItem *)LOS_MemAlloc(m_aucSysMem0, sizeof(CmdItem));
if (cmdItem == NULL) {
return OS_ERRNO_SHELL_CMDREG_MEMALLOC_ERROR;
}
(VOID)memset_s(cmdItem, sizeof(CmdItem), '\0', sizeof(CmdItem));
cmdItemNode = (CmdItemNode *)LOS_MemAlloc(m_aucSysMem0, sizeof(CmdItemNode));
if (cmdItemNode == NULL) {
(VOID)LOS_MemFree(m_aucSysMem0, cmdItem);
return OS_ERRNO_SHELL_CMDREG_MEMALLOC_ERROR;
}
(VOID)memset_s(cmdItemNode, sizeof(CmdItemNode), '\0', sizeof(CmdItemNode));
cmdItemNode->cmd = cmdItem; //命令项
cmdItemNode->cmd->cmdHook = cmdProc;//回调函数 osShellCmdLs
cmdItemNode->cmd->paraNum = paraNum;//`777`,'/home'
cmdItemNode->cmd->cmdType = cmdType;//关键字类型
cmdItemNode->cmd->cmdKey = cmdKey; //`chmod`
//2.完成构造后挂入全局链表
(VOID)LOS_MuxLock(&g_cmdInfo.muxLock, LOS_WAIT_FOREVER);
OsCmdAscendingInsert(cmdItemNode);//按升序方式插入
g_cmdInfo.listNum++;//命令总数增加
(VOID)LOS_MuxUnlock(&g_cmdInfo.muxLock);
return LOS_OK;
}
第二步 解析 | ShellTask
//shell 服务端任务初始化,这个任务负责解析和执行命令
LITE_OS_SEC_TEXT_MINOR UINT32 ShellTaskInit(ShellCB *shellCB)
{
CHAR *name = NULL;
TSK_INIT_PARAM_S initParam = {0};
//输入Shell命令的两种方式
if (shellCB->consoleID == CONSOLE_SERIAL) { //通过串口工具
name = SERIAL_SHELL_TASK_NAME;
} else if (shellCB->consoleID == CONSOLE_TELNET) {//通过远程工具
name = TELNET_SHELL_TASK_NAME;
} else {
return LOS_NOK;
}
initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)ShellTask;//任务入口函数,主要是解析shell命令
initParam.usTaskPrio = 9; /* 9:shell task priority */
initParam.auwArgs[0] = (UINTPTR)shellCB;
initParam.uwStackSize = 0x3000;
initParam.pcName = name;
initParam.uwResved = LOS_TASK_STATUS_DETACHED;
(VOID)LOS_EventInit(&shellCB->shellEvent);//初始化事件,以事件方式通知任务解析命令
return LOS_TaskCreate(&shellCB->shellTaskHandle, &initParam);//创建任务
}
LITE_OS_SEC_TEXT_MINOR UINT32 ShellTask(UINTPTR param1,
UINTPTR param2,
UINTPTR param3,
UINTPTR param4)
{
UINT32 ret;
ShellCB *shellCB = (ShellCB *)param1;
(VOID)param2;
(VOID)param3;
(VOID)param4;
while (1) {
PRINTK("\nOHOS # ");//读取shell 输入事件 例如: cat weharmony.net 命令
ret = LOS_EventRead(&shellCB->shellEvent,
0xFFF, LOS_WAITMODE_OR | LOS_WAITMODE_CLR, LOS_WAIT_FOREVER);
if (ret == SHELL_CMD_PARSE_EVENT) {//获得解析命令事件
ShellCmdProcess(shellCB);//处理命令
} else if (ret == CONSOLE_SHELL_KEY_EVENT) {//退出shell事件
break;
}
}
OsShellKeyDeInit((CmdKeyLink *)shellCB->cmdKeyLink);//
OsShellKeyDeInit((CmdKeyLink *)shellCB->cmdHistoryKeyLink);
(VOID)LOS_EventDestroy(&shellCB->shellEvent);//注销事件
(VOID)LOS_MemFree((VOID *)m_aucSysMem0, shellCB);//释放shell控制块
return 0;
}
解读
任务优先级和 客户端任务 一样同为
9
指定内核栈大小为
0x3000 = 12K
,因任务负责命令的解析和执行,所以需要更大的内核空间.任务的入口函数
ShellTask
,一个死循环在以LOS_WAIT_FOREVER
方式死等事件发生.SHELL_CMD_PARSE_EVENT
通知开始解析事件,该事件由 客户端任务ShellEntry
检测到回车键时发出.
STATIC VOID ShellNotify(ShellCB *shellCB)
{
(VOID)LOS_EventWrite(&shellCB->shellEvent, SHELL_CMD_PARSE_EVENT);
}
CONSOLE_SHELL_KEY_EVENT
收到exit
命令时将发出该事件,退出shell
回收资源
鸿蒙内核是如何管理和使用事件的请自行翻看(事件控制篇)
层层跟进
ShellCmdProcess
,解析出命令项和参数内容,最终跑到OsCmdExec
中遍历 已注册的命令表,找出命令对应的函数完成回调.LITE_OS_SEC_TEXT_MINOR UINT32 OsCmdExec(CmdParsed *cmdParsed, CHAR *cmdStr)
{
UINT32 ret;
CmdCallBackFunc cmdHook = NULL;
CmdItemNode *curCmdItem = NULL;
UINT32 i;
const CHAR *cmdKey = NULL;
if ((cmdParsed == NULL) || (cmdStr == NULL) || (strlen(cmdStr) == 0)) {
return (UINT32)OS_ERROR;
}
ret = OsCmdParse(cmdStr, cmdParsed);//解析出命令关键字,参数
if (ret != LOS_OK) {
goto OUT;
}
//遍历命令注册全局链表
LOS_DL_LIST_FOR_EACH_ENTRY(curCmdItem, &(g_cmdInfo.cmdList.list), CmdItemNode, list) {
cmdKey = curCmdItem->cmd->cmdKey;
if ((cmdParsed->cmdType == curCmdItem->cmd->cmdType) &&
(strlen(cmdKey) == strlen(cmdParsed->cmdKeyword)) &&
(strncmp(cmdKey, (CHAR *)(cmdParsed->cmdKeyword), strlen(cmdKey)) == 0)) {//找到命令的回调函数 例如: ls <-> osShellCmdLs
cmdHook = curCmdItem->cmd->cmdHook;
break;
}
}
ret = OS_ERROR;
if (cmdHook != NULL) {//执行命令,即回调函数
ret = (cmdHook)(cmdParsed->paramCnt, (const CHAR **)cmdParsed->paramArray);
}
OUT:
for (i = 0; i < cmdParsed->paramCnt; i++) {//无效的命令要释放掉保存参数的内存
if (cmdParsed->paramArray[i] != NULL) {
(VOID)LOS_MemFree(m_aucSysMem0, cmdParsed->paramArray[i]);
cmdParsed->paramArray[i] = NULL;
}
}
return (UINT32)ret;
}
第三步 | 执行
想知道有哪些系统shell
命令,可以搜索关键词SHELLCMD_ENTRY
拿到所有通过静态方式注册的命令.
其中有网络的,进程的,任务的,内存的 等等,此处列出几个常用的shell
命令的实现.
ls 命令
SHELLCMD_ENTRY(ls_shellcmd, CMD_TYPE_EX, "ls", XARGS, (CmdCallBackFunc)osShellCmdLs);
/*******************************************************
命令功能
ls命令用来显示当前目录的内容。
命令格式
ls [path]
path为空时,显示当前目录的内容。
path为无效文件名时,显示失败,提示:
ls error: No such directory。
path为有效目录路径时,会显示对应目录下的内容。
使用指南
ls命令显示当前目录的内容。
ls可以显示文件的大小。
proc下ls无法统计文件大小,显示为0。
*******************************************************/
int osShellCmdLs(int argc, const char **argv)
{
char *fullpath = NULL;
const char *filename = NULL;
int ret;
char *shell_working_directory = OsShellGetWorkingDirtectory();//获取当前工作目录
if (shell_working_directory == NULL)
{
return -1;
}
ERROR_OUT_IF(argc > 1, PRINTK("ls or ls [DIRECTORY]\n"), return -1);
if (argc == 0)//木有参数时 -> #ls
{
ls(shell_working_directory);//执行ls 当前工作目录
return 0;
}
filename = argv[0];//有参数时 -> #ls ../harmony or #ls /no such file or directory
ret = vfs_normalize_path(shell_working_directory, filename, &fullpath);//获取全路径,注意这里带出来fullpath,而fullpath已经在内核空间
ERROR_OUT_IF(ret < 0, set_err(-ret, "ls error"), return -1);
ls(fullpath);//执行 ls 全路径
free(fullpath);//释放全路径,为啥要释放,因为fullpath已经由内核空间分配
return 0;
}
task 命令
SHELLCMD_ENTRY(task_shellcmd, CMD_TYPE_EX, "task", 1, (CmdCallBackFunc)OsShellCmdDumpTask);
LITE_OS_SEC_TEXT_MINOR UINT32 OsShellCmdDumpTask(INT32 argc, const CHAR **argv)
{
UINT32 flag = 0;
#ifdef LOSCFG_KERNEL_VM
flag |= OS_PROCESS_MEM_INFO;
#endif
if (argc >= 2) { /* 2: The task shell name restricts the parameters */
goto TASK_HELP;
}
if (argc == 1) {
if (strcmp("-a", argv[0]) == 0) {
flag |= OS_PROCESS_INFO_ALL;
} else if (strcmp("-i", argv[0]) == 0) {
if (!OsShellShowTickRespo()) {
return LOS_OK;
}
goto TASK_HELP;
} else if (strcmp("-t", argv[0]) == 0) {
if (!OsShellShowSchedParam()) {
return LOS_OK;
}
goto TASK_HELP;
} else {
goto TASK_HELP;
}
}
return OsShellCmdTskInfoGet(OS_ALL_TASK_MASK, NULL, flag);
TASK_HELP:
PRINTK("Unknown option: %s\n", argv[0]);
PRINTK("usage: task or task -a\n");
return LOS_NOK;
}
cat 命令
SHELLCMD_ENTRY(cat_shellcmd, CMD_TYPE_EX, "cat", XARGS, (CmdCallBackFunc)osShellCmdCat);
/*****************************************************************
cat用于显示文本文件的内容。cat [pathname]
cat weharmony.txt
*****************************************************************/
int osShellCmdCat(int argc, const char **argv)
{
char *fullpath = NULL;
int ret;
unsigned int ca_task;
struct Vnode *vnode = NULL;
TSK_INIT_PARAM_S init_param;
char *shell_working_directory = OsShellGetWorkingDirtectory();//显示当前目录 pwd
if (shell_working_directory == NULL)
{
return -1;
}
ERROR_OUT_IF(argc != 1, PRINTK("cat [FILE]\n"), return -1);
ret = vfs_normalize_path(shell_working_directory, argv[0], &fullpath);//由相对路径获取绝对路径
ERROR_OUT_IF(ret < 0, set_err(-ret, "cat error"), return -1);
VnodeHold();
ret = VnodeLookup(fullpath, &vnode, O_RDONLY);
if (ret != LOS_OK)
{
set_errno(-ret);
perror("cat error");
VnodeDrop();
free(fullpath);
return -1;
}
if (vnode->type != VNODE_TYPE_REG)
{
set_errno(EINVAL);
perror("cat error");
VnodeDrop();
free(fullpath);
return -1;
}
VnodeDrop();
(void)memset_s(&init_param, sizeof(init_param), 0, sizeof(TSK_INIT_PARAM_S));
init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)osShellCmdDoCatShow;
init_param.usTaskPrio = CAT_TASK_PRIORITY; //优先级10
init_param.auwArgs[0] = (UINTPTR)fullpath; //入口参数
init_param.uwStackSize = CAT_TASK_STACK_SIZE;//内核栈大小
init_param.pcName = "shellcmd_cat"; //任务名称
init_param.uwResved = LOS_TASK_STATUS_DETACHED | OS_TASK_FLAG_SPECIFIES_PROCESS;
init_param.processID = 2; /* 2: kProcess */ //内核任务
ret = (int)LOS_TaskCreate(&ca_task, &init_param);//创建任务显示cat内容
if (ret != LOS_OK)
{
free(fullpath);
}
return ret;
}
你能看明白这些命令的底层实现吗? 如果看明白了,可能会不由得发出 原来如此 的感叹!
百篇博客分析.深挖内核地基
- 给鸿蒙内核源码加注释过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。
- 与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。
按功能模块:
百万汉字注解.精读内核源码
四大码仓中文注解 . 定期同步官方代码
鸿蒙研究站( weharmonyos ) | 每天死磕一点点,原创不易,欢迎转载,请注明出处。若能支持点赞则更佳,感谢每一份支持。
v72.01 鸿蒙内核源码分析(Shell解析) | 应用窥伺内核的窗口 | 百篇博客分析OpenHarmony源码的更多相关文章
- 鸿蒙内核源码分析(任务管理篇) | 任务池是如何管理的 | 百篇博客分析OpenHarmony源码 | v5.05
百篇博客系列篇.本篇为: v05.xx 鸿蒙内核源码分析(任务管理篇) | 任务池是如何管理的 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁 ...
- 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 百篇博客分析OpenHarmony源码 | v44.02
百篇博客系列篇.本篇为: v44.xx 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...
- 鸿蒙内核源码分析(内存管理篇) | 虚拟内存全景图是怎样的 | 百篇博客分析OpenHarmony源码 | v12.04
百篇博客系列篇.本篇为: v12.xx 鸿蒙内核源码分析(内存管理篇) | 虚拟内存全景图是怎样的 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有 ...
- 鸿蒙内核源码分析(进程镜像篇)|ELF是如何被加载运行的? | 百篇博客分析OpenHarmony源码 | v56.01
百篇博客系列篇.本篇为: v56.xx 鸿蒙内核源码分析(进程映像篇) | ELF是如何被加载运行的? | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应 ...
- 鸿蒙内核源码分析(根文件系统) | 先挂到`/`上的文件系统 | 百篇博客分析OpenHarmony源码 | v66.01
百篇博客系列篇.本篇为: v66.xx 鸿蒙内核源码分析(根文件系统) | 先挂到/上的文件系统 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一 ...
- 鸿蒙内核源码分析(文件系统篇) | 用图书管理说文件系统 | 百篇博客分析OpenHarmony源码 | v63.01
百篇博客系列篇.本篇为: v63.xx 鸿蒙内核源码分析(文件系统篇) | 用图书管理说文件系统 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一 ...
- 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 百篇博客分析OpenHarmony源码 | v62.01
百篇博客系列篇.本篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 51.c.h.o 本篇开始说文件系统,它是内核五大模块之一,甚至有Linux的设计哲学是" ...
- 鸿蒙内核源码分析(GN应用篇) | GN语法及在鸿蒙的使用 | 百篇博客分析OpenHarmony源码 | v60.01
百篇博客系列篇.本篇为: v60.xx 鸿蒙内核源码分析(gn应用篇) | gn语法及在鸿蒙的使用 | 51.c.h.o 编译构建相关篇为: v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙 ...
- 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 百篇博客分析OpenHarmony源码| v57.01
百篇博客系列篇.本篇为: v57.xx 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视编译全过程 | 51.c.h.o 编译构建相关篇为: v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙 ...
随机推荐
- Mybatis笔记(2)
一.Mybatis的Dao层实现 1.1 代理开发方式介绍 Mapper 接口开发需要遵循以下规范: 1. Mapper.xml文件中的namespace与mapper接口的全限定名相同 2. Map ...
- Microsoft Remote Desktop 通过 .rdp 文件登录
最近在淘宝上买了「市场洞察」子账号,说是子账号,其实是需要登录到他们的 Windows 服务器上才能用的.并且子账号也是 5-6 个人共用的,且不说远程服务器很老又有延迟,经常是我想添加一个监控店铺或 ...
- [编译] 10、kconfig 入门指导教程
目录 前言 1. 安装 kconfig 2. 克隆一个 demo 3. 运行 kconfig 4. 源码解析 4.1 选择题目设计模板 4.2 填空题目设计模板 4.3 判断题目设计模板 5. 产物解 ...
- Devexpress TreeList控件使用
增加显示列treeList1.Columns.Add(new TreeListColumn {Caption = "城市", FieldName = "Name" ...
- JS019. 原生JS使用new Blob()实现带格式导出Word、Excel(提供无编程基础将页面上表格导出到本地的方法)
导出效果 代码实现 <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...
- python模块--glob, fnmatch
包/方法 返回值 参数 说明 glob Unix shell样式的路径扩展 .glob() list 匹配满足规则的所有路径(默认以 . 开头的文件不会匹配到, 可以用 .* 来匹配) pat ...
- git各种操作:基本操作 and 多人协作 and 冲突解决
git基本操作 git 上次文件到远程仓库(参考:https://blog.csdn.net/beiqiaofeng123/article/details/104859326) 如果第一次上传,配置一 ...
- Docker安装Nginx(含:Windows启动、重启、停止)
Docker安装Nginx #docker pull nginx:latest (第一次启动Docker-Nginx) #docker run --detach \ --publish 80:80 \ ...
- Django边学边记——中间件
特点 Django中的中间件是一个轻量级.底层的插件系统,可以介入Django的请求和响应处理过程,用于全局修改Django的输入或输出. 每个中间件组件负责做一些特定的功能.中间件全部注册在sett ...
- chrome 的手机调试工具 toggle device toolbar
chrome 的手机调试工具 toggle device toolbar 是否可以模拟到不同系统,如苹果系统和安卓系统.