151 shell编程,正则表达式,在C语言中如何使用正则表达式

零,坑点记录:bash 和 dash 的区别,导致的坑点

查看当前用的shell 是啥,用的是/bin/bash

复制代码
hunandede@hunandede-virtual-machine:~$ echo $SHELL
/bin/bash

当shell 脚本运行的时候(后面会学到方法,这里是最后学完后,记录坑点),

如果我们使用的source ./001.sh 执行

或者使用的是 . ./001.sh执行

这两种方法是是一样的,都是使用当前窗口的 shell,也就是/bin/bash 执行脚本内容,并没有启动子进程去运行脚本。

在脚本使用了shell的一些相关语法的时候都没有问题。

注意这里:但是如果我们使用的**./001.sh** 运行脚本 或者是 /bin/sh ./001.sh运行脚本 ,意味着,实际上是 当前窗口启了一个子进程,来执行脚本,那么这个子进程干了啥呢?实际上是调用了 exec,exec会启动进程,如果要执行的是一个文本文件,并且第一行用Shebang指定了解释器,例如我们写的 #! /bin/sh ,那么 exec 就会调用 /bin/sh 去将脚本的每一行挨着实现。那么 /bin/sh又是一个啥呢?

我们search了/bin文件夹下的是由sh,发现,是 dash。

复制代码
hunandede@hunandede-virtual-machine:~$ ls -l /bin/sh*
lrwxrwxrwx 1 root root 4 1月  23 20:22 /bin/sh -> dash
lrwxrwxrwx 1 root root 4 1月  23 20:22 /bin/sh.distrib -> dash

这意味着,如果我们使用source 或者 . 调用,是使用的 /bin/bash 这个不起额外的进程

如果我们使用的./001.sh 调用,则使用的是 /bin/dash,这个会起额外的进程。

坑点是:dash对于很多shell 语法支持不好,因此看到很多莫名奇妙的问题。

改法一:

将第一行 从 #! /bin/sh 改动成为 #! /bin/bash

改法二:

在使用时, 使用 /bin/bash ./001.sh 运行脚本,, 而不是/bin/sh ./001.sh

一,我们在终端窗口敲入一个date,系统会返回我们一个结果,这个流程是怎么样子的呢?

复制代码
hunandede@hunandede-virtual-machine:~$ date
2024年 03月 24日 星期日 20:32:12 CST

当前终端窗口就是 黑屏幕

当我们将date 写入里面并回车的时候,shell解析器会date这个命令从PATH 的路径中挨个去找date这个命令,找到后执行date命令

可以使用echo $PATH 查看当前的PATH有哪些。

从下图可以看到,一个一个的PATH 是通过:分隔的。

这意味着,如果可执行程序date 在/home/hunandede/bin 中有,在 /usr/local/bin中也有,那么会使用 /home/hunandede/bin中的可执行程序date。

复制代码
hunandede@hunandede-virtual-machine:~$ echo $PATH
/home/hunandede/bin:/home/hunandede/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
hunandede@hunandede-virtual-machine:~$ 

编写一个简单的脚本test.sh ,并执行:

编写一个简单的脚本

复制代码
#! /bin/sh
cd ..
ls

对于这个脚本的解释:

Shell脚本中用#表示注释,相当于C语言的//注释。

但如果#位于第一行开头,并且是#!(称为Shebang)则例外,它表示该脚本使用后面指定的解释器/bin/sh解释执行

执行这个脚本:

由于脚本的执行需要 x权限

chmod是添加更改权限命令

a是指所有的用户组,包括root用户组,文件拥有者的用户组,还有其他用户组。

+x是指添加执行权限。

+x是执行权限,+r是阅读权限,+w是写入权限

复制代码
itcast$ chmod a+x test.sh
itcast$ ./test.sh

执行原理:

Shell会fork一个子进程并调用exec执行./test.sh这个程序。

exec系统调用应该把子进程的代码段替换成./test.sh程序的代码段,并从它的_start开始执行。然而test.sh是个文本文件,根本没有代码段和_start函数,怎么办呢?其实exec还有另外一种机制,如果要执行的是一个文本文件,并且第一行用Shebang指定了解释器,则用解释器程序的代码段替换当前进程,并且从解释器的_start开始执行,而这个文本文件被当作命令行参数传给解释器。因此,执行上述脚本相当于执行程序

也就是说 当我们使用

./test.sh 的时候会转移成 使用 /bin/sh 调用 ./test.sh。且是使用一个子进程 ,然后再子进程中调用exec开始这段代码的调用,那么第一个参数就是这个文件名字。

那么他的参数是什么呢?第一个参数是./test.sh

在子进程中使用exec 开始调用,那么第一个参数就是文件名。

复制代码
itcast$ /bin/sh ./test.sh

以这种方式执行不需要test.sh文件具有可执行权限。

用小括号括起来的语法,相当于在shell中起了一个子进程去执行,因此原先的路径不变:

如果将命令行下输入的命令用()括号括起来,那么也会fork出一个子Shell执行小括号中的命令,一行中可以输入由分号;隔开的多个命令,比如:

itcast$ (cd ..;ls -l)

source或者.命令是Shell的内建命令执行,这种方式也不会创建子Shell,而是直接在交互式Shell下逐行执行脚本中的命令。

也 就是说:当我们使用 **itcast source ./test.sh** 或者 itcast . ./test.sh 调用的时候(注意第一个点和第二个点中间是有 空格的) ,实际上是直接在当前窗口 直接执行shell 脚本,那么第一个参数是bash,

和上面两种方法执行Shell脚本的效果是相同的,cd ..命令改变的是子Shell的PWD,而不会影响到交互式Shell。然而命令

itcast$ cd ..;ls -l

则有不同的效果,cd ..命令是直接在交互式Shell下执行的,改变交互式Shell的PWD,然而这种方式相当于这样执行Shell脚本:

itcast$ source ./test.sh

或者

itcast$ . ./test.sh

三 基本语法

3.1 数据类型--只有一种字符串类型

在shell中只有一种数据类型,就是字符串类型。

3.2 数据种类 - 本地变量和环境变量

但是数据种类有两种,一种是 本地变量 , 一种是环境变量

3.2.1 本地变量

3.2.1.1 本地变量的定义

注意等号两边都不能有空格,否则会被Shell解释成命令和命令行参数。

复制代码
itcast$ VARNAME=value
3.2.1.2 本地变量的回显

在shell中使用 echo 可以回显您输入的字符串

使用echo &key, 可以回显key这个字符串对应的值

复制代码
//定义一个本地变量 VALUENAME 其值是value
hunandede@hunandede-virtual-machine:~$ VALUENAME=value
//使用echo $VALUENAME可以将 VALUENAME代表的值打印出来
hunandede@hunandede-virtual-machine:~$ echo $VALUENAME    
value

//使用echo,如果不加$,则只是将您输入的该字符串显示到桌面上
hunandede@hunandede-virtual-machine:~$ echo hello
hello
hunandede@hunandede-virtual-machine:~$ echo VALUENAME
VALUENAME
hunandede@hunandede-virtual-machine:~$ 
3.2.1.3 本地变量的删除 unset

unset 命令可以删除已定义的环境变量或本地变量。

同样,,unset命令也只对当前窗口生效

3.2.1.4 从本地变量变成环境变量 export

一个变量定义后仅存在于当前Shell进程,它是本地变量 ,用export命令可以把本地变量导出为环境变量,定义和导出环境变量通常可以一步完成:

复制代码
itcast$ export VARNAME=value

也可以分两步完成:

复制代码
itcast$ VARNAME=value

itcast$ export VARNAME

3.2.2 环境变量

3.2.2.1 显示当前的环境变量,使用命令为 printenv
复制代码
hunandede@hunandede-virtual-machine:~$ printenv
XDG_VTNR=7
LC_PAPER=zh_CN.UTF-8
LC_ADDRESS=zh_CN.UTF-8
XDG_SESSION_ID=c2
XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/hunandede
LC_MONETARY=zh_CN.UTF-8
CLUTTER_IM_MODULE=xim
GPG_AGENT_INFO=/home/hunandede/.gnupg/S.gpg-agent:0:1
TERM=xterm-256color
VTE_VERSION=4205
SHELL=/bin/bash
QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1
WINDOWID=58720266
LC_NUMERIC=zh_CN.UTF-8
UPSTART_SESSION=unix:abstract=/com/ubuntu/upstart-session/1000/2085
GNOME_KEYRING_CONTROL=
GTK_MODULES=gail:atk-bridge:unity-gtk-module
USER=hunandede
LD_LIBRARY_PATH=/home/hunandede/day02/dongtailib
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
LC_TELEPHONE=zh_CN.UTF-8
QT_ACCESSIBILITY=1
XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0
XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0
SSH_AUTH_SOCK=/run/user/1000/keyring/ssh
DEFAULTS_PATH=/usr/share/gconf/ubuntu.default.path
XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/usr/share/upstart/xdg:/etc/xdg
DESKTOP_SESSION=ubuntu
PATH=/home/hunandede/bin:/home/hunandede/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
QT_IM_MODULE=fcitx
QT_QPA_PLATFORMTHEME=appmenu-qt5
LC_IDENTIFICATION=zh_CN.UTF-8
XDG_SESSION_TYPE=x11
PWD=/home/hunandede
JOB=gnome-session
XMODIFIERS=@im=fcitx
GNOME_KEYRING_PID=
LANG=en_US.UTF-8
GDM_LANG=en_US
MANDATORY_PATH=/usr/share/gconf/ubuntu.mandatory.path
LC_MEASUREMENT=zh_CN.UTF-8
COMPIZ_CONFIG_PROFILE=ubuntu
IM_CONFIG_PHASE=1
PAPERSIZE=a4
GDMSESSION=ubuntu
SESSIONTYPE=gnome-session
GTK2_MODULES=overlay-scrollbar
SHLVL=1
HOME=/home/hunandede
XDG_SEAT=seat0
LANGUAGE=en_US:en
GNOME_DESKTOP_SESSION_ID=this-is-deprecated
UPSTART_INSTANCE=
UPSTART_EVENTS=started starting
XDG_SESSION_DESKTOP=ubuntu
LOGNAME=hunandede
DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-AfnsjVBsz4
XDG_DATA_DIRS=/usr/share/ubuntu:/usr/share/gnome:/usr/local/share:/usr/share:/var/lib/snapd/desktop
QT4_IM_MODULE=fcitx
LESSOPEN=| /usr/bin/lesspipe %s
INSTANCE=Unity
UPSTART_JOB=unity-settings-daemon
XDG_RUNTIME_DIR=/run/user/1000
DISPLAY=:0
XDG_CURRENT_DESKTOP=Unity
GTK_IM_MODULE=fcitx
LESSCLOSE=/usr/bin/lesspipe %s %s
LC_TIME=zh_CN.UTF-8
LC_NAME=zh_CN.UTF-8
XAUTHORITY=/home/hunandede/.Xauthority
_=/usr/bin/printenv
hunandede@hunandede-virtual-machine:~$ 
3.2.2.2 export 命名用于显示或者设置环境变量

export命令可用于显示或设置环境变量。
export命令的效果仅限于当前登录终端。

3.2.2.3思考 export 将一个本地变量变成环境变量后只能在当前窗口起作用,如果我们想重新开一个窗口也能起作用,应该怎么办呢?

四 3种方法设置环境变量

方法一:使用export命令

普通用户即可修改;仅对当前登录的终端有效。

实例:将路径/home/dabai/test/bin添加到环境变量$PATH中

export PATH=PATH:/home/dabai/test/bin

方法二:修改.bashrc文件普通用户即可修改;对当前登录用户有效。

首先,打开.bashrc文件:

$ vim ~/.bashrc

然后,在该文件中,添加如下内容:

export PATH=$PATH:/home/dabai/test/bin

最后,保存并退出;再执行如下命令,以使修改的环境变量立即生效:

$ source ~/.bashrc

方法三:修改/etc/profile文件,需要root权值;对所有用户都有效。

首先,打开profile文件:

vim /etc/profile

然后,在该文件中,添加如下内容:

export PATH=$PATH:/home/dabai/test/bin

最后,保存并退出;再执行如下命令,以使修改的环境变量立即生效:

$ source /etc/profile

source 命令解释 :

$ vim ~/.bashrc "~"代表的是当前用户的根目录,当前我登录的是hunandede ,那么~指的就是/home/hunandede这个目录,也就是说,我要改动的文件是/home/hunandede/.bashrc文件

$ source ~/.bashrc

1、source命令是什么?

source命令也称为"点命令",也就是一个点符号(.),是bash的内部命令。

注意:该命令通常用命令"."来替代

2、source命令 功能(能干什么)?

source命令通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录。因为linux所有的操作都会变成文件的格式存在。

示例:

当我修改了/etc/profile文件,我想让它立刻生效,而不用重新登录;这时就想到用source命令,如:source /etc/profile

3、source命令用法?

source filename # filename必须是可执行的脚本文件

或者

. filename # 注意"."号后面还有一个空格

注意下面的语句都是执行当前目录下的.bashrc

对应上面的理论: filename 是 ".bashrc"文件

复制代码
hunandede@hunandede-virtual-machine:~$ source .bashrc
       
hunandede@hunandede-virtual-machine:~$ . .bashrc

五 基本语法2 文件名代换(Globbing)

复制代码
	* 匹配0个或多个任意字符
	? 匹配一个任意字符
	[若干字符] 匹配方括号中任意一个字符的一次出现

itcast$ ls /dev/ttyS*
itcast$ ls ch0?.doc
itcast$ ls ch0[0-2].doc
itcast$ ls ch[012] [0-9].doc

注意,Globbing所匹配的文件名是由Shell展开的,也就是说在参数还没传给程序之前已经展开了,比如上述ls ch0012.doc命令,如果当前目录下有ch00.doc和ch02.doc,则传给ls命令的参数实际上是这两个文件名,而不是一个匹配字符串。

这是个啥意思呢?

我们当前是有多个day0x的文件夹的,如下是执行ls -l 命名的结果。

复制代码
hunandede@hunandede-virtual-machine:~$  ls -l
total 92
drwxrwxr-x  2 hunandede hunandede 4096 2月  20 18:55 aaa
drwxrwxr-x  4 hunandede hunandede 4096 2月  21 15:16 day02
drwxrwxr-x 11 hunandede hunandede 4096 2月  23 17:47 day03
drwxrwxr-x  2 hunandede hunandede 4096 2月  28 08:19 day04
drwxrwxr-x  3 hunandede hunandede 4096 2月  29 22:40 day05
drwxr-xr-x  3 hunandede hunandede 4096 3月  24 20:56 Desktop
drwxr-xr-x  3 hunandede hunandede 4096 1月  23 23:28 Documents
drwxr-xr-x  2 hunandede hunandede 4096 1月  23 21:50 Downloads
-rw-r--r--  1 hunandede hunandede 8980 1月  23 20:24 examples.desktop
drwxrwxr-x  2 hunandede hunandede 4096 2月  20 17:39 head
-rw-rw-r--  1 hunandede hunandede 1188 2月  28 08:23 list.txt
drwxr-xr-x  2 hunandede hunandede 4096 1月  23 21:50 Music
drwxrwxr-x  2 hunandede hunandede 4096 1月  23 23:47 mydocument
drwxrwxr-x  2 hunandede hunandede 4096 3月   7 20:50 new
drwxrwxr-x  3 hunandede hunandede 4096 2月  19 14:05 nginx
drwxr-xr-x  2 hunandede hunandede 4096 1月  23 21:50 Pictures
drwxrwxr-x  4 hunandede hunandede 4096 3月  24 16:12 projects
drwxr-xr-x  2 hunandede hunandede 4096 1月  23 21:50 Public
drwxr-xr-x  2 hunandede hunandede 4096 1月  23 21:50 Templates
drwxr-xr-x  2 hunandede hunandede 4096 1月  23 21:50 Videos
drwxr-xr-x  3 hunandede hunandede 4096 1月  23 21:04 下载
hunandede@hunandede-virtual-machine:~$ 

如果我们将这个命令换成 ls day*会是什么结果呢?

我们看到,所匹配的文件名,,由shell展开后,会有多个匹配,也就是说,shell传递的文件名有day02,day03,day04,day05 ,一共4个,相当于执行了 ls day02,ls day03,ls day04, ls day05 四个命令。

复制代码
hunandede@hunandede-virtual-machine:~$ ls day*
day02:
dongtailib  staticlib

day03:
gdb            makefilestudy2  makefilestudy4  makefilestudy6  makefilestudy8
makefilestudy  makefilestudy3  makefilestudy5  makefilestudy7

day04:
main.c

day05:
errorcode
hunandede@hunandede-virtual-machine:~$ 

六 基本语法3 命令代换

由"`"反引号括起来的也是一条命令,Shell先执行该命令,然后将输出结果立刻代换到当前命令行中。例如定义一个变量存放date命令的输出:

注意 反引号是 键盘上esc 下面的那个键,不要写错了

itcast$ DATE=`date`

itcast echo DATE

命令代换也可以用$()表示:

itcast DATE=(date)

复制代码
hunandede@hunandede-virtual-machine:~$ MYDATE=`date`
hunandede@hunandede-virtual-machine:~$ echo $MYDATE
2024年 03月 25日 星期一 22:11:33 CST
hunandede@hunandede-virtual-machine:~$ 

七 基本语法 4 算术代换

使用(()),用于算术计算,(())中的Shell变量取值将转换成整数,同样含义的 等价例如:

itcast$ VAR=45

itcast echo ((VAR+3)) 等价于 echo VAR+3\[VAR+3]

$(())中只能用+-*/和()运算符,并且只能做整数运算。

$base#n,其中base表示进制,n按照base进制解释,后面再有运算数,按十进制解释。

echo $2#10+11

echo $8#10+11

echo $16#10+11

复制代码
hunandede@hunandede-virtual-machine:~$ MYTIME=60
hunandede@hunandede-virtual-machine:~$ echo $[MYTIME+20]
80
hunandede@hunandede-virtual-machine:~$ echo $[16#$MYTIME+4]
100
hunandede@hunandede-virtual-machine:~$ echo $[16#60+4]
100

八 基本语法5 转义字符

和C语言类似,\在Shell中被用作转义字符,用于去除紧跟其后的单个字符的特殊意义(回车除外),换句话说,紧跟其后的字符取字面值。例如:

itcast echo SHELL

/bin/bash

itcast echo \\SHELL

$SHELL

itcast$ echo \\

\

复制代码
hunandede@hunandede-virtual-machine:~$ echo \$SHELL    不会认为$符是取值的操作符,而是单纯的一个字符$,这个字符后面又有SHELL这5个字符,因此会将 $SHELL 认为是6个字符,一起回显。
$SHELL
hunandede@hunandede-virtual-machine:~$ echo $SHELL      原本遇见$,会认为是取值的操作符,因此会从环境变量中取出SEHLL后面的值
/bin/bash
hunandede@hunandede-virtual-machine:~$ echo \\         不会认为\是和下一行连接的连接符
\
hunandede@hunandede-virtual-machine:~$ echo \          原本遇见\,会认为是和下一行 连接到一起的符号,因此会等待user在下一行输入字符串,然后回显
> aa
aa
hunandede@hunandede-virtual-machine:~$ 

比如创建一个文件名为" "的文件($间含有空格)可以这样:

itcast touch \\\ \$

还有一个字符虽然不具有特殊含义,但是要用它做文件名也很麻烦,就是-号。如果要创建一个文件名以-号开头的文件,这样是不正确的:

itcast$ touch -hello

touch: invalid option -- h

Try `touch --help' for more information.

即使加上\转义也还是报错:

itcast$ touch \-hello

touch: invalid option -- h

Try `touch --help' for more information.

因为各种UNIX命令都把-号开头的命令行参数当作命令的选项,而不会当作文件名。如果非要处理以-号开头的文件名,可以有两种办法:

itcast$ touch ./-hello

或者

itcast$ touch -- -hello

\还有一种用法,在\后敲回车表示续行,Shell并不会立刻执行命令,而是把光标移到下一行,给出一个续行提示符>,等待用户继续输入,最后把所有的续行接到一起当作一个命令执行。例如:

itcast$ ls \

> -l

(ls -l命令的输出)

九 基本语法6 单引号

和C语言同,Shell脚本中的单引号和双引号一样都是字符串的界定符(双引号下一节介绍),而不是字符的界定符。单引号用于保持引号内所有字符的字面值,即使引号内的\和回车也不例外,但是字符串中不能出现单引号。如果引号没有配对就输入回车,Shell会给出续行提示符,要求用户把引号配上对。例如:

itcast echo 'SHELL'

$SHELL

itcast$ echo 'ABC\(回车)

> DE'(再按一次回车结束命令)

ABC\

DE

复制代码
hunandede@hunandede-virtual-machine:~$ echo 'aa'
aa
hunandede@hunandede-virtual-machine:~$ echo '$SHELL'
$SHELL
hunandede@hunandede-virtual-machine:~$ echo 'ABC\DE'
ABC\DE
hunandede@hunandede-virtual-machine:~$ echo 'ABC\DE'
ABC\DE
hunandede@hunandede-virtual-machine:~$ echo 'ABC
> FD
> SD
> SS'
ABC
FD
SD
SS
hunandede@hunandede-virtual-machine:~$ echo '123\
> 456'
123\
456
hunandede@hunandede-virtual-machine:~$ echo '789\89'
789\89
hunandede@hunandede-virtual-machine:~$ 

十 基本语法7 双引号

被双引号用括住的内容,将被视为单一字串。它防止通配符扩展 ,但允许变量扩展。这点与单引号的处理方式不同

itcast DATE=(date)

itcast echo "DATE"

itcast echo 'DATE'

再比如:

itcast$ VAR=200

itcast echo VAR

200

itcast echo 'VAR'

$VAR

itcast echo "VAR"

200

十一 Shell脚本语法

条件测试

命令test $var -gt 3

可以测试一个条件是否成立,如果测试结果为真,则该命令的Exit Status为0,如果测试结果为假,则命令的Exit Status为1(注意与C语言的逻辑表示正好相反)。

例如测试两个数的大小关系:

复制代码
itcast@ubuntu:~$ var=2
itcast@ubuntu:~$ test $var -gt 1
itcast@ubuntu:~$ echo $?
0
itcast@ubuntu:~$ test $var -gt 3
itcast@ubuntu:~$ echo $?
1
itcast@ubuntu:~$ [ $var -gt 3 ]
itcast@ubuntu:~$ echo $?
1
itcast@ubuntu:~$

虽然看起来很奇怪,但左方括号 确实是一个命令的名字,传给命令的各参数之间**应该用空格隔开**,比如:$VAR、-gt、3、 命令的四个参数,它们之间必须用空格隔开。命令test或 \[ 的参数形式是相同的,只不过test命令不需要 参数。以 [ 命令为例,常见的测试命令如下表所示:

复制代码
[ -d DIR ] 如果DIR存在并且是一个目录则为真
[ -f FILE ] 如果FILE存在且是一个普通文件则为真
[ -z STRING ] 如果STRING的长度为零则为真
[ -n STRING ] 如果STRING的长度非零则为真
[ STRING1 = STRING2 ] 如果两个字符串相同则为真
[ STRING1 != STRING2 ] 如果字符串不相同则为真
[ ARG1 OP ARG2 ] ARG1和ARG2应该是整数或者取值为整数的变量,
OP是-eq(等于)-ne(不等于)-lt(小于)-le(小于等于)-gt(大于)-ge(大于等于)之中的一个

和C语言类似,测试条件之间还可以做与、或、非逻辑运算:

复制代码
[ ! EXPR ] EXPR可以是上表中的任意一种测试条件,!表示"逻辑反(非)"
[ EXPR1 -a EXPR2 ] EXPR1和EXPR2可以是上表中的任意一种测试条件,-a表示"逻辑与"
[ EXPR1 -o EXPR2 ] EXPR1和EXPR2可以是上表中的任意一种测试条件,-o表示"逻辑或"

例如

复制代码
$ VAR=abc
$ [ -d Desktop -a $VAR = 'abc' ]
$ echo $?
0

注意,如果上例中的$VAR变量事先没有定义,则被Shell展开为空字符串,会造成测试条件的语法错误(展开为 -d Desktop -a = 'abc' ),作为一种好的Shell编程习惯应该总是把变量取值放在双引号之中(展开为 -d Desktop -a "" = 'abc' ):

复制代码
$ unset VAR
$ [ -d Desktop -a $VAR = 'abc' ]
bash: [: too many arguments
$ [ -d Desktop -a "$VAR" = 'abc' ]
$ echo $?
1

分支

if/then/elif/else/fi

和C语言类似,在Shell中用if、then、elif、else、fi这几条命令实现分支控制。这种流程控制语句本质上也是由若干条Shell命令组成的,例如先前讲过的

复制代码
if [ -f ~/.bashrc ]; then

	echo isisisisi
	. ~/.bashrc
	echo aaaa
fi

其实是三条命令,

if -f ∼/.bashrc 是第一条,

then . ∼/.bashrc是第二条,

fi是第三条。

如果两条命令写在同一行则需要用;号隔开,一行只写一条命令就不需要写;号了,

另外,then后面有换行,但这条命令没写完,Shell会自动续行,把下一行接在then后面当作一条命令处理。和[命令一样,要注意命令和各参数之间必须用空格隔开。if命令的参数组成一条子命令,如果该子命令的Exit Status为0(表示真),则执行then后面的子命令,如果Exit Status非0(表示假),则执行elif、else或者fi后面的子命令。if后面的子命令通常是测试命令,但也可以是其它命令。Shell脚本没有{}括号,所以用fi表示if语句块的结束。见下例:

复制代码
#! /bin/sh

if [ -f /bin/bash ]
then 
	echo "/bin/bash is a file"
else 
	echo "/bin/bash is NOT a file"
fi

# ":"是一个特殊的命令,称为空命令,该命令不做任何事,但Exit Status总是真。

if :; then echo "always true"; fi


#此外,也可以执行/bin/true或/bin/false得到真或假的Exit Status。
if [ /bin/true ]      #判断/bin/true也会得到的是真
then echo "bin/true"
else echo "bin/false"

":"是一个特殊的命令,称为空命令,该命令不做任何事,但Exit Status总是真。

if :; then echo "always true"; fi

#此外,也可以执行/bin/true或/bin/false得到真或假的Exit Status。

if /bin/true #判断/bin/true也会得到的是真

then echo "bin/true"

else echo "bin/false"

read命令的作用是等待用户输入一行字符串,将该字符串存到一个Shell变量中。

复制代码
#! /bin/sh

echo "Is it morning? Please answer yes or no."
read YES_OR_NO
if [ "$YES_OR_NO" = "yes" ]; then
	echo "Good morning!"
elif [ "$YES_OR_NO" = "no" ]; then
	echo "Good afternoon!"
else
	echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
	exit 1
fi
exit 0

自己写的部分,试图改动一下上述的代码,让我们可以输入3次不对后就退出,但是找不到 goto语句。解决方案,应该是要在while 或者 do while里面可以完成

复制代码
# in this ,want to use number=3 control the user input wrong times,but can not success,beacuse has not "goto" 
# the Alternative solutions is use "while"  or "do while"

  
#NUMBER=3
echo "Is it morning? Please answer yes or no."
read YES_OR_NO
if [ "$YES_OR_NO" = "yes" -o "$YES_OR_NO" = "Y" -o "$YES_OR_NO" = "YES" -o "$YES_OR_NO" = "y" ]
then
	echo "Good morning!"
elif [ "$YES_OR_NO" = "no" -o "$YES_OR_NO" = "NO" -o "$YES_OR_NO" = "N" -o "$YES_OR_NO" = "n" ]
then
	echo "Good afternoon!"
#elif [ $NUMBER -gt 0 ]
#then
#	echo "Sorry, $YES_OR_NO not recognized. try again ,Enter yes or no."

#	NUMBER=$[NUMBER-1]

#	read YES_OR_NO  //notice: can not goto  
else
	echo "sorry,$YES_OR_NO not recognized. try again exit 1"
	exit 1

fi
exit 0

此外,Shell还提供了&&和||语法,和C语言类似,具有Short-circuit特性,很多Shell脚本喜欢写成这样:

test "$(whoami)" != 'root' && (echo you are using a non-privileged account; exit 1)

&&相当于"if...then...",而||相当于"if not...then..."。&&和||用于连接两个命令,而上面讲的-a和-o仅用于在测试表达式中连接两个测试条件,要注意它们的区别,例如:

test "VAR" -gt 1 -a "VAR" -lt 3

和以下写法是等价的

test "VAR" -gt 1 \&\& test "VAR" -lt 3

case/esac

case命令可类比C语言的switch/case语句,esac表示case语句块的结束。C语言的case只能匹配整型或字符型常量表达式,而Shell脚本的case可以匹配字符串和Wildcard,每个匹配分支可以有若干条命令,末尾必须以;;结束,执行时找到第一个匹配的分支并执行相应的命令,然后直接跳到esac之后,不需要像C语言一样用break跳出。

复制代码
#! /bin/sh

echo "Is it morning? Please answer yes or no."
read YES_OR_NO
case "$YES_OR_NO" in
yes|y|Yes|YES)
	echo "Good Morning!";;
[nN]*)
	echo "Good Afternoon!";;
*)
	echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
	exit 1;;
esac
exit 0

循环

for/do /done

Shell脚本的for循环结构和C语言很不一样,它类似于某些编程语言的foreach循环。例如:

复制代码
#! /bin/sh

for FRUIT in apple banana pear; do
	echo "I like $FRUIT"
done

FRUIT是一个循环变量,第一次循环$FRUIT的取值是apple,第二次取值是banana,第三次取值是pear。

再比如,要将当前目录下的chap0、chap1、chap2等文件名改为chap0~、chap1~、chap2~等(按惯例,末尾有~字符的文件名表示临时文件),这个命令可以这样写:

复制代码
$ for FILENAME in chap?; do mv $FILENAME $FILENAME~; done

也可以这样写:

复制代码
$ for FILENAME in `ls chap?`; do mv $FILENAME $FILENAME~; done
while/do/done

while的用法和C语言类似。比如一个验证密码的脚本:

复制代码
#! /bin/sh

echo "Enter password:"
read TRY
while [ "$TRY" != "secret" ]; do
	echo "Sorry, try again"
	read TRY
done

下面的例子通过算术运算控制循环的次数:

复制代码
#! /bin/sh

COUNTER=1
while [ "$COUNTER" -lt 10 ]; do
	echo "Here we go again"
	COUNTER=$(($COUNTER+1))
done

另,Shell还有until循环,类似C语言的do...while。如有兴趣可在课后自行扩展学习。

break和continue

breakn可以指定跳出几层循环;continue跳过本次循环,但不会跳出循环。

即break跳出,continue跳过。

练习:将上面验证密码的程序修改一下,如果用户输错五次密码就报错退出。

位置参数和特殊变量

有很多特殊变量是被Shell自动赋值的,我们已经遇到了?和1。其他常用的位置参数和特殊变量在这里总结一下:

复制代码
$0 			相当于C语言main函数的argv[0]
$1、$2...	这些称为位置参数(Positional Parameter),相当于C语言main函数的argv[1]、argv[2]...
$# 			相当于C语言main函数的argc - 1,注意这里的#后面不表示注释
$@ 			表示参数列表"$1" "$2" ...,例如可以用在for循环中的in后面。
$* 			表示参数列表"$1" "$2" ...,同上
$? 			上一条命令的Exit Status
$$ 			当前进程号

位置参数可以用shift命令左移。比如shift 3表示原来的4现在变成1,原来的5现在变成2等等,原来的1、2、3丢弃,0不移动。不带参数的shift命令相当于shift 1。例如:

复制代码
#! /bin/sh

echo "The program $0 is now running"
echo "The first parameter is $1"
echo "The second parameter is $2"
echo "The parameter list is $@"
shift
echo "The first parameter is $1"
echo "The second parameter is $2"
echo "The parameter list is $@"

输入输出

echo

显示文本行或变量,或者把字符串输入到文件。

复制代码
echo [option] string
-e 解析转义字符
-n 不回车换行。默认情况echo回显的内容后面跟一个回车换行。
echo "hello\n\n"
echo -e "hello\n\n"
echo "hello"
echo -n "hello"

运行结果为 我们看到 加了-e,会有两个回车。 加了-n 不会再次回车

复制代码
hello\n\n
hello


hello
hellohunandede@hunandede-virtual-machine:~/Desktop/shell$ 
管道

可以通过 | 把一个命令的输出传递给另一个命令做输入。

复制代码
cat myfile | more
ls -l | grep "myfile"
df -k | awk '{print $1}' | grep -v "文件系统"
df -k 查看磁盘空间,找到第一列,去除"文件系统",并输出
tee

tee命令把结果输出到标准输出,另一个副本输出到相应文件

使用*"df -k"*命令,以KB为单位显示磁盘使用量和占用率

df -k | awk '{print $1}' | grep -v "文件系统" | tee a.txt

tee -a a.txt表示追加操作。

df -k | awk '{print $1}' | grep -v "文件系统" | tee -a a.txt

文件重定向
复制代码
cmd > file 				把标准输出重定向到新文件中
cmd >> file 			追加
cmd > file 2>&1 		标准出错也重定向到1所指向的file里
cmd >> file 2>&1
cmd < file1 > file2 	输入输出都定向到文件里
cmd < &fd 				把文件描述符fd作为标准输入
cmd > &fd 				把文件描述符fd作为标准输出
cmd < &- 				关闭标准输入

例如

ls -la > a.txt

函数

和C语言类似,Shell中也有函数的概念,但是函数定义中没有返回值也没有参数列表。例如:

复制代码
#! /bin/sh

foo(){ echo "Function foo is called";}
echo "-=start=-"
foo
echo "-=end=-"

注意函数体的左花括号 { 和后面的命令之间必须有空格或换行,如果将最后一条命令和右花括号 } 写在同一行,命令末尾必须有分号;。但,不建议将函数定义写至一行上,不利于脚本阅读。

在定义foo()函数时并不执行函数体中的命令,就像定义变量一样,只是给foo这个名一个定义,到后面调用foo函数的时候(注意Shell中的函数调用不写括号)才执行函数体中的命令。Shell脚本中的函数必须先定义后调用,一般把函数定义语句写在脚本的前面,把函数调用和其它命令写在脚本的最后(类似C语言中的main函数,这才是整个脚本实际开始执行命令的地方)。

Shell函数没有参数列表并不表示不能传参数,事实上,函数就像是迷你脚本,调用函数时可以传任意个参数,在函数内同样是用0、1、2等变量来提取参数,函数中的位置参数相当于函数的局部变量,改变这些变量并不会影响函数外面的0、1、2等变量。函数中可以用return命令返回,如果return后面跟一个数字则表示函数的Exit Status。

下面这个脚本可以一次创建多个目录,各目录名通过命令行参数传入,脚本逐个测试各目录是否存在,如果目录不存在,首先打印信息然后试着创建该目录。

函数传递参数的写法

下面这个脚本可以一次创建多个目录,各目录名通过命令行参数传入,脚本逐个测试各目录是否存在,如果目录不存在,首先打印信息然后试着创建该目录。

注意:is_directory()返回0表示真返回1表示假。

复制代码
#! /bin/sh

is_directory()
{
	DIR_NAME=$1
	if [ ! -d $DIR_NAME ]; then
		return 1
	else
		return 0
	fi
}
for DIR in "$@"; do
	if is_directory "$DIR"
	then :
	else
		echo "$DIR doesn't exist. Creating it now..."
		mkdir $DIR > /dev/null 2>&1
		if [ $? -ne 0 ]; then
			echo "Cannot create directory $DIR"
			exit 1
		fi
	fi
done

Shell脚本调试方法

Shell提供了一些用于调试脚本的选项,如:

-n 读一遍脚本中的命令但不执行,用于检查脚本中的语法错误。

-v 一边执行脚本,一边将执行过的脚本命令打印到标准错误输出。

-x 提供跟踪执行信息,将执行的每一条命令和结果依次打印出来。

这些选项有三种常见的使用方法:

  1. 在命令行提供参数。如:

$ sh -x ./script.sh

  1. 在脚本开头提供参数。如:

#! /bin/sh -x

  1. 在脚本中用set命令启用或禁用参数。如:

    #! /bin/sh

    if [ -z "$1" ]; then
    set -x
    echo "ERROR: Insufficient Args."
    exit 1
    set +x
    fi

set -x和set +x分别表示启用和禁用-x参数,这样可以只对脚本中的某一段进行跟踪调试。

正则表达式:

注意 正则表达式的目的是从 一段文本中 查找我们想要的内容。

是模糊匹配的。

以前我们用grep在一个文件中找出包含某些字符串的行,比如在头文件中找出一个宏定义。其实grep还可以找出符合某个模式(Pattern)的一类字符串。例如找出所有符合xxxxx@xxxx.xxx模式的字符串(也就是email地址),要求x字符可以是字母、数字、下划线、小数点或减号,email地址的每一部分可以有一个或多个x字符,例如abc.d@ef.com、1_2@987-6.54,当然符合这个模式的不全是合法的email地址,但至少可以做一次初步筛选,筛掉a.b、c@d等肯定不是email地址的字符串。再比如,找出所有符合yyy.yyy.yyy.yyy模式的字符串(也就是IP地址),要求y是0-9的数字,IP地址的每一部分可以有1-3个y字符。

egrep找的是包含某一模式的行。

grep 或者 egrep 是一种查找过滤工具,正则表达式在grep中用来查找符合模式的字符串。其实正则表达式还有一个重要的应用是验证用户输入是否合法,例如用户通过网页表单提交自己的email地址,就需要用程序验证一下是不是合法的email地址,这个工作可以在网页的Javascript中做,也可以在网站后台的程序中做,例如PHP、Perl、Python、Ruby、Java或C,所有这些语言都支持正则表达式,可以说,目前不支持正则表达式的编程语言实在很少见。除了编程语言之外,很多UNIX命令和工具也都支持正则表达式,例如grep、vi、sed、awk、emacs等等。"正则表达式"就像"变量"一样,它是一个广泛的概念,而不是某一种工具或编程语言的特性。

一般用法

当前以egrep命令来学习正则表达式

复制代码
$ egrep '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' testfile

egrep 是 grep的加强版

解析:

egrep 是命令;

用两个单引号 ' xxx ' 包裹起来的部分是正则表达式;

后面的testfile 是文件名, 表示要从那个文件中查找。

我们主要学习的就是 两个单引号 中间的部分怎么写。

语法:

位置限定符:就是我要以啥开头,或者以啥结尾,或者什么类型的开头或者结尾

例子:我们是要从一段文字(或者文本文件)中获得想要的匹配的字符,那么写例子之前我们就先要有一个 文本文件 。

该文本文件名字为zhengze.txt,内容如下,注意是有 空行的。

复制代码
www.baidu.com
198.289.987.987

hunandede

hunan

nihaohunan

adsfhunanasdf

nishuo

89.890.908.90.908

90.78.678.213

nasndf  asdf  hun a sd f


youde shuo

ypp78.89asdf.89as.90

nias678.87.asdf67asd.89

end

字符类

数量限定符

其它特殊字符

C语言处理正则表达式函数

POSIX规定了正则表达式的C语言库函数,详见regex(3)。我们已经学习了很多C语言库函数的用法,读者应该具备自己看懂man手册的能力了。本章介绍了正则表达式在grep、sed、awk中的用法,学习要能够举一反三,请读者根据regex(3)自己总结正则表达式在C语言中的用法,写一些简单的程序,例如验证用户输入的IP地址或email地址格式是否正确。

C语言处理正则表达式常用的函数有regcomp()、regexec()、regfree()和regerror(),一般分为三个步骤,如下所示:

C语言中使用正则表达式一般分为三步:

编译正则表达式 regcomp()

匹配正则表达式 regexec()

释放正则表达式 regfree()

下边是对三个函数的详细解释

这个函数把指定的正则表达式pattern编译成一种特定的数据格式compiled,这样可以使匹配更有效。函数regexec 会使用这个数据在目标文本串中进行模式匹配。执行成功返回0。

int regcomp (regex_t *compiled, const char *pattern, int cflags)

regex_t 是一个结构体数据类型,用来存放编译后的正则表达式,它的成员re_nsub 用来存储正则表达式中的子正则表达式的个数,子正则表达式就是用圆括号包起来的部分表达式。

pattern 是指向我们写好的正则表达式的指针。

cflags 有如下4个值或者是它们或运算(|)后的值:

REG_EXTENDED 以功能更加强大的扩展正则表达式的方式进行匹配。

REG_ICASE 匹配字母时忽略大小写。

REG_NOSUB 不用存储匹配后的结果,只返回是否成功匹配。如果设置该标志位,那么在regexec将忽略nmatch和pmatch两个参数。

REG_NEWLINE 识别换行符,这样'$'就可以从行尾开始匹配,'^'就可以从行的开头开始匹配。

当我们编译好正则表达式后,就可以用regexec 匹配我们的目标文本串了,如果在编译正则表达式的时候没有指定cflags的参数为REG_NEWLINE,则默认情况下是忽略换行符的,也就是把整个文本串当作一个字符串处理。

执行成功返回0。

regmatch_t 是一个结构体数据类型,在regex.h中定义:

typedef struct {

regoff_t rm_so;

regoff_t rm_eo;

} regmatch_t;

成员rm_so 存放匹配文本串在目标串中的开始位置,rm_eo 存放结束位置。通常我们以数组的形式定义一组这样的结构。因为往往我们的正则表达式中还包含子正则表达式。数组0单元存放主正则表达式位置,后边的单元依次存放子正则表达式位置。

int regexec (regex_t *compiled, char *string, size_t nmatch, regmatch_t matchptr\[\], int eflags)

compiled 是已经用regcomp函数编译好的正则表达式。

string 是目标文本串。

nmatch 是regmatch_t结构体数组的长度。

matchptr regmatch_t类型的结构体数组,存放匹配文本串的位置信息。

eflags 有两个值:

REG_NOTBOL 让特殊字符^无作用

REG_NOTEOL 让特殊字符$无作用

当我们使用完编译好的正则表达式后,或者要重新编译其他正则表达式的时候,我们可以用这个函数清空compiled指向的regex_t结构体的内容,请记住,如果是重新编译的话,一定要先清空regex_t结构体。

void regfree (regex_t *compiled)

当执行regcomp 或者regexec 产生错误的时候,就可以调用这个函数而返回一个包含错误信息的字符串。

size_t regerror (int errcode, regex_t *compiled, char *buffer, size_t length)

errcode 是由regcomp 和 regexec 函数返回的错误代号。

compiled 是已经用regcomp函数编译好的正则表达式,这个值可以为NULL。

buffer 指向用来存放错误信息的字符串的内存空间。

length 指明buffer的长度,如果这个错误信息的长度大于这个值,则regerror 函数会自动截断超出的字符串,但他仍然会返回完整的字符串的长度。所以我们可以用如下的方法先得到错误字符串的长度。

例如: size_t length = regerror (errcode, compiled, NULL, 0);

测试用例:

#include <sys/types.h>

#include <regex.h>

#include <stdio.h>

int main(int argc, char ** argv)

{

if (argc != 3) {

printf("Usage: %s RegexString Text\n", argv0);

return 1;

}

const char * pregexstr = argv1;

const char * ptext = argv2;

regex_t oregex;

int nerrcode = 0;

char szerrmsg1024 = {0};

size_t unerrmsglen = 0;

if ((nerrcode = regcomp(&oregex, pregexstr, REG_EXTENDED|REG_NOSUB)) == 0) {

if ((nerrcode = regexec(&oregex, ptext, 0, NULL, 0)) == 0) {

printf("%s matches %s\n", ptext, pregexstr);

regfree(&oregex);

return 0;

}

}

unerrmsglen = regerror(nerrcode, &oregex, szerrmsg, sizeof(szerrmsg));

unerrmsglen = unerrmsglen < sizeof(szerrmsg) ? unerrmsglen : sizeof(szerrmsg) - 1;

szerrmsgunerrmsglen = '\0';

printf("ErrMsg: %s\n", szerrmsg);

regfree(&oregex);

return 1;

}

匹配网址:

./a.out "http:\/\/www\..*\.com" "http://www.taobao.com"

匹配邮箱:

./a.out "^a-zA-Z0-9+@a-zA-Z0-9+.a-zA-Z0-9+" "itcast123@itcast.com"

./a.out "\w+(-+.\w+)*@\w+(-.\w+)*\.\w+(-.\w+)*" "itcast@qq.com"

注:\w匹配一个字符,包含下划线

匹配固话号码:请同学们自己编写。

除了gnu提供的函数外,还常用PCRE处理正则,全称是Perl Compatible Regular Ex-pressions。从名字我们可以看出PCRE库是与Perl中正则表达式相兼容的一个正则表达式库。PCRE是免费开源的库,它是由C语言实现的,这里是它的官方主页:http://www.pcre.org/,感兴趣的朋友可以在这里了解更多的内容。 要得到PCRE库,可以从这里下载:http://sourceforge.net/projects/pcre/files/

PCRE++是一个对PCRE库的C++封装,它提供了更加方便、易用的C++接口。这里是它的官方主页:http://www.daemon.de/PCRE,感兴趣的朋友可以在这里了解更多的内容。 要得到PCRE++库,可以从这里下载:http://www.daemon.de/PcreDownload

另外c++中常用 boost regex。

一堆练习题

export , set , printenv的区别

set命令用于显示所有的环境变量和方法

用set命令可以显示当前Shell进程中定义的所有变量(包括本地变量和环境变量)和函数。

相关推荐
AOwhisky3 分钟前
Python 学习笔记(第十四期)——运维自动化(下·中篇):远程文件传输——paramiko进阶篇
运维·python·学习·云原生·自动化·文件传输·paramiko
imc.1133 分钟前
linux基础IO
linux·运维·服务器
躺不平的理查德34 分钟前
让Ubuntu 的第二块网卡 ens37 永久固定为 192.168.30.111
运维·服务器
bitbrowser1 小时前
Facebook 验证反复回到原页面,应该从哪里排查
运维·服务器·facebook
Land03291 小时前
AI网页元素变化无法自动修复?自带元素自愈的自动化解决方案
运维·人工智能·ai·自动化·rpa
fengyehongWorld1 小时前
Jenkins 安装与简单配置
运维·jenkins
云智慧AIOps社区2 小时前
2026 国产化 ITSM 替代指南:横向测评 ServiceNow、轻帆云、Jira等五款主流IT服务管理平台
运维·人工智能·运维开发·it服务管理·itsm平台
糖果店的幽灵2 小时前
langgraph的分支结构之 - 静态分支详解
java·服务器·数据库·langgraph
虹科网络安全2 小时前
艾体宝新闻|从 SQL 注入到服务器接管:CVE-2026-57517 暴露 Web 管理面板的供应链与安全编码风险
服务器·前端·sql
Urbano2 小时前
针织卫衣全流程生产工序科普:自动化替代、产能优化与设备选型实战方案
运维·自动化