Linux Shell

Linux Shell基础

shell是名词,具体实现了shell的软件成为bash,其他编程语言

Shell的作用:

  • 解释执行用户输入的命令或程序。
  • 用户输入一条命令,Shell就解释一条。
  • 键盘输入命令,Linxu给与响应的方式成为交互式。

Shell是一块包着系统核心的壳,处于操作系统的最外层,与用户直接对话,把用户的输入,解释给操作系统,然后处理操作系统返回的结果,输出到屏幕上与用户看到结果。

Shell脚本比较适合处理纯文本类型的数据,比如日志,配置文件,文本,网页文件,大多数都是纯文本类型的,因此shell可以方便的进行文本处理

什么是Shell脚本

当命令或者程序语句写在文件当中,我们执行文件,读取其中的代码,这程序文件称为shell脚本。

在shell脚本里定义多条Linux命令以及循环控制语句,然后将这些Linux命令一次性执行完毕,执行脚本文件的方式称为,非交互方式。

Windwos中存在“.bat”批处理脚本

Linux中常用“.sh”脚本文件

shell脚本规则

在Linux系统中,shell脚本(bash shell 程序)通常使用vim进行编辑,由Linux 命令,bash shell指令,逻辑控制语句核注释信息组成。

shebang

计算机程序中,shebang指的是出现在文本文件的第一行前两个字符#!

在Unix系统中,程序会分析shebang后面的内容,作为解释器的指令,例如

  • #!/bin/sh开头的文件,程序在执行时会使用/bin/sh,也就是bash解释器
  • #!/usr/bin/python开头的文件,代表指定python解释器去执行
  • #!/usr/bin/env解释器名称,是一种在不同平台上都正确找到解释器的办法

注意事项

  • 如果脚本未指定shebang,脚本执行的时候,默认用当前shell去解释脚本,即$HELL
  • 如果shebang指定了可执行的解释器,如/bin/bash /usr/bin/python,脚本在执行时,文件名会作为参数传递给解释器
  • 如果#!指定的解释器程序没有可执行的权限,则会报错“bad interpreter:Permission denied”.
  • 如果#!指定解释程序不是一个可执行文件,那么指定的解释器程序会被忽略,转而交给当前的$SHELL去执行这个脚本。
  • 如果#!指定的解释程序不存在,那么会报错“bad interpreter:No such file or directory”
  • #!之后的解释程序,需要写其绝对路径(如:#!/bin/bash),他是不会自动到$PATH中寻找解释器的
  • 如果你使用“bash test.sh”这样的命令来执行脚本,那么#!这一行会被忽略掉,解释器使用命令中显式指定的bash

脚本注释,脚本开发规范

# shebang指定解释器
#! /bin/bash

# 注释内容
# 这是为们的第一个shell脚本

# 打印内容
echo "我是shell个脚本"

执行Shell脚本的方式

直接输入shell脚本名		系统会去pase 文件内寻找执行
./shell脚本名			系统在当前文件架下寻找执行(需要运行权限, chmod +x shell文件名)
/bin/bash shell脚本名		指定解释器执行文件
source shell脚本名
. shell脚本名
bash < shell脚本名

脚本语言

shell脚本语言属于一种弱类型语言无需声明变量类型直接定义使用

强类型语言,必须先声明变量类型,之后再赋予同类型的值

ubuntu系统中支持的shell情况,有如下种类

$ cat /etc/shells 
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/usr/bin/sh
/bin/dash
/usr/bin/dash

默认的sh解释器

$ ll /usr/bin/sh
lrwxrwxrwx 1 root root 4 Sep 25 09:24 /usr/bin/sh -> dash*

Shell的优势

Linux底层命令都支持shell语句,结合三剑客进行更高级的应用

擅长脚本开发,软件停启,监控报警,日志分析

#Linux默认shell
$ echo $SHELL
/bin/bash

bash基础特性

bash是什么

  • bash是一个命令处理器,运行在文本窗口中,并能执行用户直接输入的命令
  • bash还能从文件中读取Linux命令,称为脚本
  • bash支持通配符,管道,命令替换,条件判断等逻辑控制语句

bash特性汇总

  • 文件路径tab键补全

  • 命令补全

  • 快捷键

  • 通配符

  • 命令历史

    • shell会保留会话其中用户提交执行的命令
$ history # 查看命令历史 -c:清空内存中命令历史, -r:从文件中恢复历史命令, 数字:显示最近n条命令 history 10
ls
...
history

$ echo $HISTSIZE # 可以修改保存的命令历史个数
1000

$ echo $HISTFILE # 存放历史命令的文件,用户退出登陆后,持久化命令个数
/home/si/.bash_history

$ ls -a ~/.bash_history
/home/si/.bash_history

$ ! 1960  # 感叹号加历史id执行历史命令
$ !! # 感叹号+感叹号执行上一次命令
  • 命令别名
  • 命令行展开

变量含义

数字字母下划线
$ name=123
$ echo ${name}
123

$ pstree  # 查看进程树

Shell变量

作用域

  • 本地变量,只针对当前shell生效
  • 环境变量,全局变量,对所有的shell生效
  • 局部变量,针对在shell函数或者shell脚本中定义的

位置参数变量

用于shell脚本中传递参数

特殊变量

shell内置的特殊功效的变量

$ $?
0:成功 1-255:错误码

自定义变量

变量赋值		varName=value
变量引用		${varName} $varName

双引号可以进行替换,单引号为普通字符串

不同的执行方式,不同的Shell环境

每次调用bash/sh解释器执行脚本,都会开启一个子shell,因此不会保留当前的shell变量,通过pstree命令检查进程树

调用source是在当前的shell环境中加载脚本,因此保留变量

Shell变量面试题

父子shell

$ cat he.sh 
var=`whoami`
$ echo $var

$ sh he.sh 
$ echo $var

$ . he.sh 
$ echo $var
si

环境变量设置

环境变量一般指的是用export内置命令导出的变量,用于定义shell的运行环境,保证shell命令的正确执行

shell通过环境变量确定登陆的用户名,PATH路径,文件系统等各种应用

环境变量可以在命令行中临时创建,但是用户退出shell终端,变量即丢失,如果要永久生效,则需要修改环境变量配置文件

  • 用户个人配置文件~/.bash_profile,~/.bashrc远程用户特有
  • 全局配置文件/etc/profile /etc/bashrc,且系统建议最好创建在/etc/profile.d/,而非直接修改主文件,修改全局配置文件,影响所有登陆系统的用户。

检查系统环境变量的命令

set			输出所有变量,包括全局变量和局部变量(脚本文件中的变量)
env			只显示全局变量
declare		输出所有的变量,同set
export		显示和设置环境变量值
unset 		变量名,撤销删除变量或函数
readonly	设置变量为只读,shell结束变量失效

bash多命令执行

$ export | awk -F '[ :=]' '{print $3}'
------------
COLORTERM
CONDA_DEFAULT_ENV
...

环境变量初始化与加载顺序

  1. ssh登陆Linux后,系统启动一个bash shell,bash会读取若干个系统环境文件,检查环境变量设置
  2. /etc/profile:全局环境变量文件,为系统的每个用户设置环境信息,当用户第一次登陆时,该文件被执行,并从/etc/profile.d目录的配置文件中收集shell的设置
  3. 然后读取/etc/profile.d目录下的脚本有系统诸多脚本,也放入自定义需要登陆加载的脚本便于用于登陆后立即运行脚本
  4. 运行$HOME/.bash_profile(用户环境变量文件)
  5. 运行$HOME/.bashrc
  6. 运行/etc/bashrc

取出变量值

  • 单引号:所见即所得
  • 双引号:输出引号里所有内容,识别特殊符号,弱引用
  • 无引号:连续的符号可以不加引号,有空格则有歧义,最好用双引号
  • 反引号:引用命令执行结果,等于$()用法

特殊变量

shell的特殊变量,用在脚本,函数传递参数使用,有如下特殊的,位置参数变量

$0    获取shell脚本文件名,以及脚本路径
$n    获取shell脚本的第n个参数,n在1-9之间,如$1,$2,$9,大于9则需要写,¥{10},输入时参数空格隔开
$#    获取执行shell脚本后面的参数总个数
$*    获取shell脚本所有参数,不加引号等同于$@作用,加上引号"$*"作用是 接收所有参数为单个字符串,如"$1 $2..."
$@    不加引号,效果同上,加引号,是接收所有参数为独立字符串,如"$1""$2"...空格保留
$!    获取上次后台执行的pid
$$    获取当前执行的pid
$_    获取上次传入的最后一个参数

实践

$ cat text_var.sh
#! /bin/bash
echo '特殊变量 $0 $1 $2'
echo $0 $1 $2
echo '------------'
echo '特殊变量 $#'
echo $#
echo '------------'
echo '特殊变量 $*'
echo $*
echo '------------'
echo '特殊变量 $@'
echo $@
$ bash text_vat.sh one two 180 180 180 180
特殊变量 $0 $1 $2
text_ver.sh one two
------------
特殊变量 $#
6
------------
特殊变量 $*
one two 180 180 180 180
------------
特殊变量 $@
one two 180 180 180 180
$ cat text_ver.sh 
[ $# -ne 2 ] && {
    echo "aaaa"
    exit 119
}
echo ok
$ ./text_ver.sh 
aaaa
$ ./text_ver.sh as as
ok

Shell子串

bash一些基础的内置命令

echo    # 打印内容 默认自带换行    平替printf函数
------------
-n    不换行输出
-e    解析字符串中的特殊符号
\n    换行
\r    回车
\t    指标符
\b    退格
##################
eval    # 一次执行多个命令
------------
$ eval ls;cat text_ver.sh
text_ver.sh
[ $# -ne 2 ] && {
    echo "aaaa"
    exit 119
}
echo ok
##################
exec    # 不创建子进程,执行后续命令,且执行完毕后,自动exit
------------
$ sh
$ exec date
Sat Oct 21 10:25:46 PM CST 2023
##################
export    # 显示和设置环境变量值 
##################
read
##################
read
##################
unset name    # 取消指定的变量
##################
time    # 统计命令时长
------------
$ time ls
...
real    0m0.004s    # 实际运行的时间
user    0m0.000s    # 用户态执行的时间
sys    0m0.004s    # 内核态执行的时间
##################
for number in {1..100}    # for 循环语句
do
    echo $number
down
------------
$ for num in {1..100};do echo $num ; done
##################
seq
------------
$ seq -s ":" 10
1:2:3:4:5:6:7:8:9:10
##################

shell子串的花式用法

${变量}    返回变量值
------------
$ name=wordname
$ echo ${name}
wordname
##################
${#变量}    返回变量长度,字符长度,速度最快
------------
$ name=wordname
$ echo ${#name} == $ echo $name | wc -L == $ expr length $name == $ echo "${name}" | awk '{print length($0)}'
8
##################
${变量:4}    从4索引开始返回
------------
$ name=wordname
echo ${name:4}
name
##################
${变量:4:1}    从4索引开始,输出1个字符长度
------------
$ name=wordname
$ echo ${name:4:2}
na
##################
${变量#word}    从变量开头开始删除最短匹配的word子串,支持模糊匹配
------------
$ name=abcABC123ABCabc
$ echo ${name#a*c}
ABC123ABCabc
##################
${变量##word}    从变量开头开始删除最长匹配的word子串,支持模糊匹配
------------
$ name=abcABC123ABCabc
$ echo ${name##a*c}

##################
${变量%word}    从变量结尾开始删除最短匹配的word子串,支持模糊匹配
------------
$ name=abcABC123ABCabc
$ echo ${name%a*c}
abcABC123ABC
##################
${变量%%word}    从变量结尾开始删除最长匹配的word子串,支持模糊匹配
------------
$ name=abcABC123ABCabc
$ echo ${name%%a*c}
wordname
##################
${变量/pattern/string}    从string代替第一个匹配的pattern,支持模糊匹配
------------
$ name=abcABC123ABCabc
$ echo ${name/a*c/xyz}
xyz
$ echo ${name/abc/xyz}
xyzABC123ABCabc
##################
${变量//pattern/string}    从string代替所有的pattern,支持模糊匹配
------------
$ name=abcABC123ABCabc
~$ echo ${name//abc/xyz}
xyzABC123ABCxyz
##################
result=${变量:-word}    变量为空返回word字符串赋值给result变量
------------
$ result=${sy:-heihei}
$ echo $result; echo $sy
heihei
##################
result=${变量:=word}    变量为空word字符串替代变量值,且返回值
------------
$ result=${sy:=heihei}
 echo $result $sy
heihei heihei
##################
result=${变量:?word}    变量为空word当作stderr输出,否则输出变量值,用于设置变量为空时返回错误信息
------------
$ result=${sy:?heihei}
bash: sy: heihei
$ echo $result $sy

$ sy=123
$ result=${sy:?heihei}
$ echo $result $sy
123 123
##################
result=${变量:+word}    变量为空,什么都不作,否则返回word
------------
$ result=${sy:+heihei}
$ echo $result $sy

$ sy=123
$ result=${sy:+heihei}
$ echo $result $sy
heihei 123

实践

$ touch sy_{1..5}_finished.jpg
$ touch sy_{1..5}_finished.png
$ ls
sy_1_finished.jpg  sy_2_finished.png  sy_4_finished.jpg  sy_5_finished.png
sy_1_finished.png  sy_3_finished.jpg  sy_4_finished.png
sy_2_finished.jpg  sy_3_finished.png  sy_5_finished.jpg

$ for n in `ls *_fin*.jpg`;do mv $n ${n//_finished/};done
$ ls *.jpg
sy_1.jpg  sy_2.jpg  sy_3.jpg  sy_4.jpg  sy_5.jpg

进程列表

可以实现并发执行,提高执行效率

shell的进程列表理念,需要使用小括号(),如下执行,这样就称为进程列表

$ (cd ~;pwd;cd /tmp/;pwd;ls)

BASH_SUBSHELL变量,取值,如果是0则为父shell

$ (cd ~;pwd;cd /tmp/;pwd;ls;echo $BASH_SUBSHELL)
------------
...
1

子进程嵌套

$ (cd ~;pwd;(cd /tmp/;pwd;echo $BASH_SUBSHELL);echo $BASH_SUBSHELL)
------------
...
2    # 第2层子shell
1    # 第1层子shell

内置命令,外置命令

  • 内置命令:在系统启动时就加入内存,常驻内存,执行效率更高,但是占资源

    • 不会产生子进程,内置命令和shell是为一体的,是shell的一部分,不需要单独去读取某个文件,系统启动后就执行在内存中。
    • compgen -b # 查看shell内置命令
  • 外置命令:系统需要从硬盘中读取程序文件,再读入内存加载

  • 父进程衍生出子进程,子进程执行外置命令。

shell脚本开发

为了方便执行多行命令

编写Shebang指定解释器

执行脚本的方式

直接输入shell脚本名		系统会去pase 文件内寻找执行
./shell脚本名			系统在当前文件架下寻找执行(需要运行权限, chmod +x shell文件名)
/bin/bash shell脚本名	指定解释器执行文件
source shell脚本名
. shell脚本名
bash < shell脚本名

echo 在liunx下进行格式化打印

echo "${date}\n`date`\n$(date)"
------------
$var   引用变量替换 
${}    引用变量替换
``    执行命令并获取结果
$()    在子解释器中执行并获取结果

算术运算

(())    用于整数计算
------------
((i=i+1))
i=$((i+1))
((8>7&&5==5))
echo $((2+1))
##################
let    用于整数计算,等同于(())但是没有它效率高
------------
firstnum=1
$ let firstnum=$firstnum+1
$ echo $firstnum
2
##################
expr    用于整数计算,还有其他功能
------------
必须是以传入参赛的形式进行
$ expr 5 \+ 3 # 数学符号需要转义
$ expr --help
$ expr sy.png ":" ".*\.png"
7
‍```‍```
#! /bin/bash
if expr "$1" ":" ".*\.jpg" &> /dev/null
 then
  echo "这是jpg结尾的"
 else
  echo "这不是jpg结尾的"
fi
‍```‍```
##################
bc    Linux下的计算器(适合整数和小数运算)
------------
$ bc # 进入交互式计算器
$ num=5
$ result=`echo $num*5`
$ echo $result
5*5
$ result=`echo $num*5|bc`
$ echo $result
25
$ echo {1..100} |tr " " "+"
1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23+24+25+26+27+28+29+30+31+32+33+34+35+36+37+38+39+40+41+42+43+44+45+46+47+48+49+50+51+52+53+54+55+56+57+58+59+60+61+62+63+64+65+66+67+68+69+70+71+72+73+74+75+76+77+78+79+80+81+82+83+84+85+86+87+88+89+90+91+92+93+94+95+96+97+98+99+100
$ echo {1..100} |tr " " "+" |bc
5050
$ seq -s "+" 100
1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23+24+25+26+27+28+29+30+31+32+33+34+35+36+37+38+39+40+41+42+43+44+45+46+47+48+49+50+51+52+53+54+55+56+57+58+59+60+61+62+63+64+65+66+67+68+69+70+71+72+73+74+75+76+77+78+79+80+81+82+83+84+85+86+87+88+89+90+91+92+93+94+95+96+97+98+99+100
$ seq -s "+" 100 |bc
5050
##################
$[]    用于整数计算
------------
$ res=$[5+4]
##################
awk    awk即可用于整数计算,也可小数计算
------------
$ echo "3.2 2.2" |awk '{print ($1*$2))}'
##################
declare    定义变量和属性值,-i参数可以用于定义整形变量,作运算

条件测试

test <条件表达式>
[ 条件表达式 ] # 和test是一样的
------------
$ test -e filename
$ [ -e filename ]
-e 文件名是否 存在
-f 文件名是否 是文件
-d 文件名是否 是目录
-s 文件名是否 是非空文件
-b 文件名是否 为一个block device装置
-c 文件名是否 为一个character device装置
-S 文件名是否 为一个Socket文件
-p 文件名是否 为一个FIFO(pipe)文件
-L 文件名是否 为一个连结档
-r 文件名是否 有可读属性
-w 文件名是否 有可写属性
-x 文件名是否 有可执行属性
-u 文件名是否 有SUID属性
-g 文件名是否 有SGID属性
-k 文件名是否 有Sticky bit属性
$ test file1 -nt file2
$ [ file1 -nt file2 ]
-nt 1是否比2新
-ot 1是否比2旧
-ef 1是否和2是同一个文件,主要用意是判断两个文件是否指向一个inode
$ test n1 -eq n2 
$ [ n1 -eq n2 ]
-eq 两数值相等
-ne 两数值不相等
-gt n1大于n2
-lt n1小于n2
-ge n1大于等于n2
-le n1小于等于n2
$ test -z string # 判定字符串是否为空,空为true
[ -z string ]
$ test -n string # 判定字符串是否为空,非空为true
[ -n string ]
$ test str1 = str2 # 判定字符串是否相等,相等为true
[ str1 = str2 ]
$ test str1 != str2 # 判定字符串是否相等,不等为true
[ str1 != str2 ]
$ test -r filename -a -x filename 
[ -r filename -a -x filename ]
-a 俩个情况同时成立
-o 两个情况一个成立
! 两个情况刚好相反
##################
[[]] # 是[]的加强,支持直接使用数学符号,支持正则
------------
##################
(())
------------
##################

定义函数

shell脚本自上而下执行加载

执行shell函数直接写函数名即可,无需添加其他内容,函数必须先定义,再执行.

函数体内定义的变量为局部变量

函数体内需要加return语句作用是退出函数,且赋予赋予返回值给调用该函数的程序,也就是shell加本(在shell脚本中,定义,使用函数,shell脚本执行结束后,通过$?获取其return的返回值)

return语句和返回值不同,(return只能写在函数中,exit是shell内置命令,用于退出shell环境)

return是结束函数的执行,返回一个退出值/返回值

exit是结束shell环境,返回一个退出值/返回值给当前的shell

函数如果单独写在文件中,需要用source读取(函数的另一种用法,写入文件中,用于加载)

函数内,使用local关键字定义局部变量。

##########
同文件内定义并调用函数
$ cat test.sh 
#! /bin/bash

# 脚本开发
# 函数的作用,就算把写好的功能代码进行打包,封装成一个函数名,需要使用该功能直接调用函数名即可。

# 函数的名字
print_usage(){
echo "你好"
}

# 执行该函数
print_usage

$ /bin/bash test.sh
你好
##########
其他文件引用执行函数
$ vim my_foc.sh
sa(){
    echo "我是函数sa 我被调用了"
}
# 方法一
$ source */myfoc.sh # 加载定义的函数到变量中
# 方法二
$ vim func1.sh
./*/my_foc.sh # 加载函数到文件中
sa

检查网站是否存活函数

#! /bin/bash

# 监测网站存活
CheckUrl(){
  # 超时等待时间
  timeout=5
  # 访问成功还是失败,相当于计数器
  fails=0
  success=0

  # 循环执行命令
  while true
    do
      wget --timeout=${timeout}  --tries=1 http://www.baidu.com -q -o ~/桌面/testlog.log
      if [ $? -ne 0 ] # 不等于
        then
          let fails=fails+1
      else
        let success+=1
      fi
      if [ $success -ge 1 ] # 大于等于
        then
          echo "运行中"
          exit 0
      fi
      if [ $fails -ge 2 ];then
        echo "停止中"
        exit 2
      fi
  done
}

CheckUrl

实战训练

定时监测内存

# 1.获取当前内存,获取available数据, 第二行最后一个字段
FreeMem=`free -m |awk 'NR==2 {print $NF}'`
CHARS="内存剩余 $FreeMem"
# 2.判断小于多少
if [ "$FreeMem" -lt "100" ]
 then
  echo $CHARS|tee /home/si/桌面/messages.txt
  # 3.使用mail服务发送邮件
  mail -s "`date +%F-%T`$CHARS" elm_security@163.com < /home/si/桌面/messages.txt
fi
# 4.加入定时任务
crontab -e # 编辑定时任务

C程序调用shell脚本

共有三种方法 :system()、popen()、exec系列函数

1. system()

不用你自己去产生进程,它已经封装了,直接加入自己的命令

例如:system("ls -l /etc")

2. exec()系列函数

需要自己 fork 进程,然后exec 自己的命令

#include <unistd.h>
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */ );
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, .../* (char *)0, char *const envp[] */ );
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
// 如果filename中包含/,则将其视为路径名,否则就按PATH环境变量,在它所指的各目录搜寻可执行文件.

返回值:如果执行成功将不返回,否则返回-1,失败代码存储在errno中.

前4个函数取路径名作为参数,后两个是取文件名作为参数,最后一个是以一个文件描述符作为参数.

execl()函数用来执行参数path字符串所指向的程序,第二个及以后的参数代表执行文件时传递的参数列表,最后一个参数必须是空指针以标志参数列表的结束。

int execl(const char *path, const char *arg0, ... /* (char *)0 */ );
例如:
int execl("/bin/ls","ls","-l","/etc",(char *)0)

execv()函数函数用来执行参数path字符串所指向的程序,第二个为数组指针维护的程序参数列表,该数组的最后一个成员必须是空指针.

int execv(const char *path, char *const argv[]);
例如:
#include <unistd.h>  
int main()  
{  
        char *argv[] = {"ls", "-l", "/etc", (char *)0};  
        execv("/bin/ls", argv);  
        return 0;  
}

execlp()函数会从PATH环境变量所指的目录中查找文件名为第一个参数指示的字符串,找到后执行该文件,第二个及以后的参数代表执行文件时传递的参数列表,最后一个参数必须是空指针.

int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );

execvp()函数会从PATH环境变量所指的目录中查找文件名为第一个参数指示的字符串,找到后执行该文件,第二个参数代表执行文件时传递的参数列表,最后一个成员必须是空指针.

int execvp(const char *file, char *const argv[]);
例如:
#include <unistd.h>
void main()
{
        char *argv[] = {"ls","-l", "/home/kaiming",(char *)0};
        execvp("ls",argv);    #环境变量只有/bin,所以这里的第一个参数还要写“ls”
}

exec系列函数通常需要和fork函数一起使用:

fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

  1. 在父进程中,fork返回新创建子进程的进程ID;
  2. 在子进程中,fork返回0;
  3. 如果出现错误,fork返回一个负值;
#include <unistd.h>  
#include <stdio.h>  
#include <sys/types.h>  
#include <stdlib.h>  
父进程
int main(){  
	pid_t pid;  
	pid = fork();          //pid =子进程的ID
	if(pid<0){
		printf("error fork:%m\n");  
		exit(-1);  
	}  
	else if(pid==0){
		execl("/bin/ls","ls","-l","/etc",(char *)0);  
	}  
	else{
		printf("parent process\n");  
	}  
	return 0;
}

//子进程
	pid = fork();     //pid=0
	if(pid<0){
		printf("error fork:%m\n");  
		exit(-1);
	}
	else if(pid==0){
		execl("/bin/ls","ls","-l","/etc",(char *)0);  
	}
	else{
		printf("parent process\n");  
	}
	return 0;  
}  

在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行command命令。

type参数只能是"r"或者"w"中的一种,得到的返回值(标准I/O流)也具有和type相应的只读或只写类型。如果type是"r"则文件指针连接到command的标准输出;如果type是"w"则文件指针连接到command的标准输入。

command参数是一个指向以NULL结束的shell命令字符串的指针。这行命令将被传到/bin/sh并使用-c标志,shell将执行这个命令。

返回值:若成功则返回文件指针,否则返回NULL,差错原因存于errno中。返回值是个标准I/O流,必须由pclose来终止

popen()函数

FILE * popen(char *command, char *type)

函数fgets()从给出的文件流中读取[num - 1]个字符并且把它们转储到str(字符串)中. fgets()在到达行末时停止,在这种情况下,str(字符串)将会被一个新行符结束. 如果fgets()达到[num - 1]个字符或者遇到EOF, str(字符串)将会以null结束.fgets()成功时返回str(字符串),失败时返回NULL.

#include <stdlib.h>
#include <stdio.h>
#define BUF_SIZE 1024
char buf[BUF_SIZE];
int main(void)
{
    FILE * p_file = NULL;
    p_file = popen("ifconfig", "r");        #因为返回值是"标准I/O流",所以并不会在屏幕上显示结果,必须配合fgets()和fprintf()才能显示结果
    if (!p_file) {
        fprintf(stderr, "Erro to popen");
    }
    while(fgets(buf, BUF_SIZE, p_file)!=NULL){
        fprintf(stdout, "%s", buf);
    }
    pclose(p_file);

    return 0;
}

 char *fgets( char *str, int num, FILE *stream );fprintf()函数根据指定的format(格式)(格式)发送信息(参数)到由stream(流)指定的文件. fprintf()只能和printf()一样工作. fprintf()的返回值是输出的字符数,发生错误时返回一个负值.

fprintf()函数根据指定的format(格式)(格式)发送信息(参数)到由stream(流)指定的文件. fprintf()只能和printf()一样工作. fprintf()的返回值是输出的字符数,发生错误时返回一个负值.

#include <stdlib.h>
#include <stdio.h>
#define BUF_SIZE 1024
char buf[BUF_SIZE];
int main(void)
{
    FILE * p_file = NULL;
    p_file = popen("ifconfig", "r");        #因为返回值是"标准I/O流",所以并不会在屏幕上显示结果,必须配合fgets()和fprintf()才能显示结果
    if (!p_file) {
        fprintf(stderr, "Erro to popen");
    }
    while(fgets(buf, BUF_SIZE, p_file)!=NULL){
        fprintf(stdout, "%s", buf);
    }
    pclose(p_file);

    return 0;
}

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

fprintf()函数根据指定的format(格式)(格式)发送信息(参数)到由stream(流)指定的文件. fprintf()只能和printf()一样工作. fprintf()的返回值是输出的字符数,发生错误时返回一个负值.

int fprintf( FILE *stream, const char *format, ... );

C语言标准I/O库中的操作都是围绕流进行的。当我们打开或创建一个文件时,就会将一个流与该文件关联。在流式的I/O操作中,系统会为操作分配缓冲区,以减少对read、write的调用次数。

posted @ 2025-03-27 13:54  *--_-  阅读(48)  评论(0)    收藏  举报