Linux编程笔记3【个人用】

目录

一、Shell

1.shell是什么

[1.1 shell脚本](#1.1 shell脚本)

[1.2 shell环境](#1.2 shell环境)

2.shell的使用

[2.1 创建test.sh文件](#2.1 创建test.sh文件)

[2.2 运行shell脚本](#2.2 运行shell脚本)

3.shell变量

[3.1 使用变量](#3.1 使用变量)

[3.2 注释](#3.2 注释)

[3.3 字符串](#3.3 字符串)

[3.4 数组](#3.4 数组)

4.运算符

[4.1 expr](#4.1 expr)

[4.2 算术运算符](#4.2 算术运算符)

[4.3 关系运算符](#4.3 关系运算符)

[4.4 布尔运算符](#4.4 布尔运算符)

[4.5 逻辑运算符](#4.5 逻辑运算符)

[4.6 字符运算符](#4.6 字符运算符)

[4.7 文件测试运算符](#4.7 文件测试运算符)

5.参数传递

[5.1 特殊参数说明](#5.1 特殊参数说明)

6.脚本中调用命令

7.流程语句

[7.1 if语句](#7.1 if语句)

[7.2 for循环](#7.2 for循环)

[7.3 while循环](#7.3 while循环)

[7.4 case](#7.4 case)

[7.5 break和continue](#7.5 break和continue)

二、文件IO编程

1.系统调用

[1.1 API接口](#1.1 API接口)

[1.2 内核](#1.2 内核)

[1.3 系统调用步骤](#1.3 系统调用步骤)

[1.4 为什么不能直接访问内核服务?](#1.4 为什么不能直接访问内核服务?)

[1.5 系统调用包含在Linux的libc库中,通过标准的C函数调用方法调用](#1.5 系统调用包含在Linux的libc库中,通过标准的C函数调用方法调用)

[1.6 系统命令属于什么?](#1.6 系统命令属于什么?)

2.文件系统架构

[2.1 虚拟文件系统VFS](#2.1 虚拟文件系统VFS)

[2.2 文件描述符](#2.2 文件描述符)

[3. 常用函数](#3. 常用函数)

[3.1 open](#3.1 open)

[3.2 creat](#3.2 creat)

[3.3 close](#3.3 close)

[3.4 read](#3.4 read)

[3.5 write](#3.5 write)

[5.6 lseek](#5.6 lseek)

[5.7 pread](#5.7 pread)

[5.8 pwrite](#5.8 pwrite)

pread/pwirte和read/write的区别

[5.9 ioctl](#5.9 ioctl)

三、标准IO库

1.有了系统API,为什么还要标准I/O库?

2.常见的标准IO库函数

[2.1 fopen](#2.1 fopen)

[2.2 fclose](#2.2 fclose)

[2.3 fread](#2.3 fread)

[2.4 fwrite](#2.4 fwrite)

[2.5 fgetc](#2.5 fgetc)

[2.6 fgets](#2.6 fgets)

[2.7 fputs](#2.7 fputs)

[2.8 fseek](#2.8 fseek)

[2.9 ftell](#2.9 ftell)

[2.10 rewind](#2.10 rewind)


一、Shell

1.shell是什么

Shell 是一个用C语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种脚本语言(编程语言)。

1.1 shell脚本

常说的shell除了指shell命令,更多指的是shell脚本(shell script)。业界所说的 shell 通常都是指 shell 脚本。Shell 编程跟 JavaScript、php 编程一样,都是脚本语言,不需要提前编译即可运行。

1.2 shell环境

为了编写shell脚本,我们需要编辑器以及脚本解释器。在Linux下常见的解释器有:

  • Bourne Shell(/usr/bin/sh或/bin/sh)
  • Bourne Again Shell(/bin/bash)
  • C Shell(/usr/bin/csh)
  • K Shell(/usr/bin/ksh)
  • Shell for Root(/sbin/sh)

2.shell的使用

2.1 创建test.sh文件

bash 复制代码
 #!/bin/sh
  
echo "hello world"
  • 使用**#!指定当前脚本使用的解释器**,这是必须声明的!
  • echo打印字符串

2.2 运行shell脚本

  • 作为可执行程序运行
bash 复制代码
chmod +x ./test.sh  #使脚本具有执行权限
./test.sh  #执行脚本
  • 作为解释器参数
bash 复制代码
/bin/sh test.sh

3.shell变量

定义shell变量名的规则与其他语言基本一致,规则如下:

  • 命名只能使用英文、数字、下划线,且首字母不能以数字开头
  • 不能使用bash内的关键字做变量名
  • 注意变量名和等于号之间不能有空格

3.1 使用变量

引用变量需要在加**$符号,并且推荐使用带花括号的引用方式**

bash 复制代码
name="dalong"
echo $name
echo ${name}
echo "hello,my name is ${name}"

二次赋值时,不再需要$

bash 复制代码
name="hello"

使用readonly可以将变量定义为只读变量,在此之后,变量值就不能被改变,否则会报错

bash 复制代码
name="dalong"
readonly name  #只读

使用unset删除变量,但是不可以删除readonly过的变量

bash 复制代码
name="dalong"
unset name

3.2 注释

bash 复制代码
#注释以井号开头

#多行注释
<< EOF
Cmd line 1
Cmd line 2
Cmd line 3
Cmd line 4
EOF

3.3 字符串

bash 复制代码
str1='dalong'
str2="dalong2"

#双引号字符串中可以直接使用变量
name="dalong"
str3="my name is ${name}}"
#双引号可以使用转移字符\
str4="Hello,  \"${name}\"! \n"

#拼接双引号字符串
hello1="hello,"${name}"!"
hello2="hello,${name}!"
#拼接单引号字符串
hello2='hello,'${name}'!'

#获取字符串长度
echo ${#name}

#截取字符串第2-4个字符
echo ${name:1:4}

#查找a最早出现的位置(这里是反引号)
echo `expr index "$name" a`

3.4 数组

Bash 只支持一维数组,下标从0开始,数组的元素用小括号存放、空格或回车隔开。

bash 复制代码
#定义一个名为arrays的数组,并且存入12345
arrays=(1 2 3 4 5)

#单独定义数组的各个元素
arrays[0]=1
arrays[1]=2

#获取数组内所有元素
echo ${arrays[@]}

#获取数组长度
length=${#arrays[@]}
length=${#arrays[*]}
#获取数组单个元素的长度
length=${#arrays[n]}

4.运算符

shell中有算术运算符、关系运算符、布尔运算符、字符串运算符、文件测试运算符

原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。

4.1 expr

Shell expr 是一个功能强大,并且比较复杂的命令,它除了可以实现整数计算,还可以结合一些选项对字符串进行处理,例如计算字符串长度、字符串比较、字符串匹配、字符串提取等。

4.2 算术运算符

假设a=10,b=20

|-----|---------------------------|------------------------------|
| 运算符 | 说明 | 举例 |
| + | 加法 | `expr a + b` 结果为 30。 |
| - | 减法 | `expr a - b` 结果为 -10。 |
| * | 乘法 | `expr a \\\* b` 结果为 200。 |
| / | 除法 | `expr b / a` 结果为 2。 |
| % | 取余 | `expr b % a` 结果为 0。 |
| = | 赋值 | a=b 把变量 b 的值赋给 a。 | | == | 相等。用于比较两个数字,相同则返回 true。 | \[ a == b \] 返回 false。 | | != | 不相等。用于比较两个数字,不相同则返回 true。 | \[ a != $b ] 返回 true。 |

bash 复制代码
result=`expr 3 + 4 - 2 + 4 \* 5 / 2`  
#需要使用反引号、数字与运算符使用空格隔开、乘号需要转义字符
echo ${result}

4.3 关系运算符

|-----|-------------------------------|---------------------------|
| 运算符 | 说明 | 举例 |
| -eq | 检测两个数是否相等,相等返回 true。 | $a -eq $b 返回 false。 |
| -ne | 检测两个数是否不相等,不相等返回 true。 | $a -ne $b 返回 true。 |
| -gt | 检测左边的数是否大于右边的,如果是,则返回 true。 | $a -gt $b 返回 false。 |
| -lt | 检测左边的数是否小于右边的,如果是,则返回 true。 | $a -lt $b 返回 true。 |
| -ge | 检测左边的数是否大于等于右边的,如果是,则返回 true。 | $a -ge $b 返回 false。 |
| -le | 检测左边的数是否小于等于右边的,如果是,则返回 true。 | $a -le $b 返回 true。 |

bash 复制代码
if [2 -eq 4]
then
        echo "2等于4"
else
        echo "2不等于4"
fi

4.4 布尔运算符

|-----|------------------------------------|-----------------------------------------|
| 运算符 | 说明 | 举例 |
| ! | 非运算,表达式为 true 则返回 false,否则返回 true。 | ! false 返回 true。 |
| -o | 或运算,有一个表达式为 true 则返回 true。 | $a -lt 20 -o $b -gt 100 返回 true。 |
| -a | 与运算,两个表达式都为 true 才返回 true。 | $a -lt 20 -a $b -gt 100 返回 false。 |

4.5 逻辑运算符

|------|---------|-----------------------------------------------|
| 运算符 | 说明 | 举例 |
| && | 逻辑的 AND | \[ $a -lt 100 \&\& $b -gt 100 ] 返回 false |
| || | 逻辑的 OR | \[ $a -lt 100 \|\| $b -gt 100 ] 返回 true |

4.6 字符运算符

假定变量 a 为 "hello",变量 b 为 "spoto":

|-----|-----------------------------|-------------------------|
| 运算符 | 说明 | 举例 |
| = | 检测两个字符串是否相等,相等返回 true。 | $a = $b 返回 false。 |
| != | 检测两个字符串是否不相等,不相等返回 true。 | $a != $b 返回 true。 |
| -z | 检测字符串长度是否为0,为0返回 true。 | -z $a 返回 false。 |
| -n | 检测字符串长度是否不为 0,不为 0 返回 true。 | -n "$a" 返回 true。 |
| | 检测字符串是否不为空,不为空返回 true。 | \[ a ] 返回 true。 |

4.7 文件测试运算符

文件测试运算符用于检测文件的各种属性。

属性检测描述如下:

|---------|------------------------------------------|--------------------------|
| 操作符 | 说明 | 举例 |
| -b file | 检测文件是否是块设备文件,如果是,则返回 true。 | -b $file 返回 false。 |
| -c file | 检测文件是否是字符设备文件,如果是,则返回 true。 | -c $file 返回 false。 |
| -d file | 检测文件是否是目录,如果是,则返回 true。 | -d $file 返回 false。 |
| -f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 | -f $file 返回 true。 |
| -g file | 检测文件是否设置了 SGID 位,如果是,则返回 true。 | -g $file 返回 false。 |
| -k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 | -k $file 返回 false。 |
| -p file | 检测文件是否是有名管道,如果是,则返回 true。 | -p $file 返回 false。 |
| -u file | 检测文件是否设置了 SUID 位,如果是,则返回 true。 | -u $file 返回 false。 |
| -r file | 检测文件是否可读,如果是,则返回 true。 | -r $file 返回 true。 |
| -w file | 检测文件是否可写,如果是,则返回 true。 | -w $file 返回 true。 |
| -x file | 检测文件是否可执行,如果是,则返回 true。 | -x $file 返回 true。 |
| -s file | 检测文件是否为空(文件大小是否大于0),不为空返回 true。 | -s $file 返回 true。 |
| -e file | 检测文件(包括目录)是否存在,如果是,则返回 true。 | -e $file 返回 true。 |

5.参数传递

在执行脚本的时候,可以直接向脚本内传递参数,脚本获取参数的格式为:$n

n代表传递的参数个数,我们可以使用0、1、2、3......n访问

bash 复制代码
echo "获取第一个参数$0"
echo "获取第一个参数$1"
echo "获取第一个参数$2"
echo "获取第一个参数$3"

5.1 特殊参数说明

|-----|-----------------------------------------------------------------------------|
| # | 传递的参数个数 | | * | 以一个单字符串显示所有向脚本传递的参数。 如"\*"用「"」括起来的情况、以"1 2 ... n"的形式输出所有参数。 |
| $$ | 脚本运行的当前进程ID号 |
| ! | 后台运行的最后一个进程的ID号 | | @ | 与\*相同,但是使用时加引号,并在引号中返回每个参数。 如"@"用「"」括起来的情况、以"1" "2" ... "n" 的形式输出所有参数。 | | - | 显示Shell使用的当前选项 |
| $? | 显示最后命令的退出状态。0表示没有错误,其他值表明有错误。 |

6.脚本中调用命令

bash 复制代码
#直接执行
pwd

#使用反引号``将命令引用,可以执行以及获取返回值
cmd=`pwd`
echo "当前执行的pwd命令结果为:${cmd}"

7.流程语句

  • 数字判断:相等(-eq)、大于(-gt)、小于(-lt)、大于等于(-ge)、小于等于(-le)
  • &&、||、!、-o、-a
  • -b、-d、-c

7.1 if语句

bash 复制代码
#单个if判断a<b
a=1
b=2
if [ $a -lt $b ]
then
        echo "a小于b"
fi

 
#用if和else判断字符串是否为空
string=""
if [ -z $string ];then
     echo kong 
 else
     echo not kong 
fi

a=130
b=250
if [ $a == $b ]
then
        echo "a==b"
elif [ $a -gt $b ]
then
        echo "a > b"
elif [ $a -lt $b ]
then    
        echo "a < b"
else
        echo "a b not known"
fi

7.2 for循环

bash 复制代码
for 变量名 in 取值列表
do
  命令
done


#遍历数组
arrays=(1 2 3 4 5)
for i in ${arrays[*]}
do      
        echo "print value is:${i}"
done

#使用C的语法
arrays=(1 2 3 4 5)
for ((i = 0;i<=5;i++))
do      
        echo "print value is:${i}"
done

#输出ls命令的结果
cmd=`ls`
for i in $cmd
do
    echo $i
done

7.3 while循环

bash 复制代码
while expression
do 
     command
done


i=10
j=0
#判断i>j,条件成立则执行代码
while [ $i -gt $j ]
do
     echo $i
     i=$(($i - 1))  #这句代码相当于C语言中的i--,格式必须这样。
done

#无限循环
while :
do
    echo "hello wolrd"
done

#无限循环
while true
do
    echo "hello wolrd"
done

#无限循环
while (1)
do 
     command
done

7.4 case

bash 复制代码
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read num 
case $num in
    1)  echo '你选择了 1'
    ;;
    2)  echo '你选择了 2'
    ;;
    3)  echo '你选择了 3'
    ;;
    4)  echo '你选择了 4'
    ;;
    *)  echo '你没有输入 1 到 4 之间的数字'
    ;;
esac

7.5 break和continue

shell中berak和continue的作用分别是跳出所有分支、跳出本次循环

bash 复制代码
for  i   in    1 2 3 4 5

do

if [ "$i" -eq 3 ]

then break

else echo "$i"

 fi

done
bash 复制代码
for  i   in    1 2 3 4 5

do

if [ "$i" -eq 3 ]

then continue

else echo "$i"

 fi

done

二、文件IO编程

1.系统调用

系统调用就是一种特殊的接口**(API)**。通过这个接口,用户可以访问内核空间。

1.1 API接口

API(Application Programming Interface)应用程序接口,是一些预先定义的函数,提供应用程序与开发人员基于某软件或硬件的以访问一组例程的能力,无需访问源码,或理解内部工作机制的细节。

一个api可能会需要一个或多个系统调用来完成特定功能。

1.2 内核

内核是操作系统的核心组件,是计算机物理硬件与其上运行的进程之间的主要接口。内核通过提供对 CPU、内存、磁盘 I/O 和网络的访问,使多个应用程序能够共享硬件资源。 想象一台计算机由一系列层组成,最内层是硬件,最外层是运行在计算机上的软件应用程序。在这个类比中,内核位于硬件和应用程序之间,因为它不仅负责管理硬件资源和执行软件程序,还负责监督这些层之间的交互。

而内核才是操作系统的内在核心, 系统的其他部分必须依靠内核这部分软件提供的服务, 像管理硬件设备和分配系统资源等。在linux中,将程序的运行空间分为内核与用户空间(内核态和用户态),在逻辑上它们之间是相互隔离的,因此用户程序不能访问内核数据,也无法使用内核函数

1.3 系统调用步骤

步骤:用户进程------系统调用------内核------返回用户空间

1.4 为什么不能直接访问内核服务?

为了更好地保护内核空间,将程序的运行空间分为内核空间和用户空间(也就是常称的内核态和用户态),它们分别运行在不同的级别上,在逻辑上是相互隔离的。因此,用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间的函数。

1.5 系统调用包含在Linux的libc库中,通过标准的C函数调用方法调用

1.6 系统命令属于什么?

系统命令的本质是一个可执行程序,内部调用了接口来实现相应的功能。

cpp 复制代码
//比如cp命令的源代码
#include <stdio.h>              /* standard input/output routines.    */

#define MAX_LINE_LEN 1000    /* maximal line length supported.     */

/*
 * function: main. copy the given source file to the given target file.
 * input:    path to source file and path to target file.
 * output:   target file is being created with identical contents to
 *           source file.
 */
void main(int argc, char* argv[])
{
    char* file_path_from;    /* path to source file.   */
    char* file_path_to;        /* path to target file.   */
    FILE* f_from;        /* stream of source file. */
    FILE* f_to;            /* stream of target file. */
    char buf[MAX_LINE_LEN+1];   /* input buffer.          */

    /* read command line arguments */
    if (argc != 3 || !argv[1] || !argv[2]) {
    fprintf(stderr, "Usage: %s <source file path> <target file path>\n",
        argv[0]);
    exit(1);
    }
    file_path_from = argv[1];
    file_path_to = argv[2];

    /* open the source and the target files. */
    f_from = fopen(file_path_from, "r");
    if (!f_from) {
    fprintf(stderr, "Cannot open source file: ");
    perror("");
    exit(1);
    }
    f_to = fopen(file_path_to, "w+");
    if (!f_to) {
    fprintf(stderr, "Cannot open target file: ");
    perror("");
    exit(1);
    }

    /* copy source file to target file, line by line. */
    while (fgets(buf, MAX_LINE_LEN+1, f_from)) {
    if (fputs(buf, f_to) == EOF) {  /* error writing data */
        fprintf(stderr, "Error writing to target file: ");
        perror("");
        exit(1);
    }
    }
    if (!feof(f_from)) { /* fgets failed _not_ due to encountering EOF */
        fprintf(stderr, "Error reading from source file: ");
        perror("");
        exit(1);
    }

    /* close source and target file streams. */
    if (fclose(f_from) == EOF) {
    fprintf(stderr, "Error when closing source file: ");
    perror("");
    }
    if (fclose(f_to) == EOF) {
    fprintf(stderr, "Error when closing target file: ");
    perror("");
    }
}

2.文件系统架构

2.1 虚拟文件系统VFS

虚拟文件系统是内核中的一个软件层,对内实现文件系统的抽象,允许不同的文件系统共存,对外向应用程序提供统一的接口。通过统一的文件系统调用API即可对系统中任意文件进行操作,而不需要考虑具体的文件系统类型。

  • 超级块(super block):用于存储文件系统的控制信息的数据结构。描述文件系统的状态、文件系统类型、大小、区块数、索引节点数等,存放于磁盘的特定扇区中。
  • 索引节点(inode):用于存储文件的元数据(文件的基本信息)的一个数据结构,包含诸如文件的大小、拥有者、创建时间、磁盘位置等信息。
  • 目录项(dentry):目录被用来容纳文件,目录可以包含子目录,层层嵌套以形成文件路径。
  • 文件对象(file):一组在逻辑上具有完整意义的信息项的系列。

2.2 文件描述符

内核通过文件描述符来区分特定的文件。

文件描述符是一个非负整数,它是一个索引值,指向在内核中每个进行打开文件的记录表。每当我们打开一个文件或新建一个文件时,内核就向我们返回一个文件描述符,我们需要通过文件描述符来操作具体的文件。

3. 常用函数

可以使用 man 2 函数名 来查看函数的用法

  • open:打开或创建文件,可以指定文件的属性以及用户权限等参数
  • creat:打开一个文件,如果文件不存在,则创建它
  • close:关闭一个被打开的文件。
  • read:将从指定的文件描述符中读出的数据放到指定的位置,并返回实际读取的字节数。
  • write:向打开的文件写入数据。

3.1 open

打开或创建文件,可以指定文件的属性以及用户权限等参数

cpp 复制代码
头文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

函数原型
int open( const char * pathname, int flags);
int open( const char * pathname,int flags, mode_t mode);

函数说明
参数pathname 指向想要打开的文件路径字符串。

参数flags 常用的标识:    
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以可读写方式打开文件。
上述三种标识是互斥的,也就是不可同时使用,但可与下列的标识利用OR(|)运算符组合。
O_CREAT 若欲打开的文件不存在则自动建立该文件。
O_TRUNC 若文件存在并且以可写的方式打开时,此标识会令文件长度清为0,而原来存于该文件的资料也会消失。
O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。
O_NONBLOCK 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。
O_EXCL: 如果同时指定 O_CREAT,而该文件又是存在的,报错;也可以测试一个文件是否存在,不存在则创建。

返回值
文件打开成功返回文件的描述符,失败返回-1

3.2 creat

打开一个文件,如果文件不存在,则创建它。

cpp 复制代码
头文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

函数原型
int creat(const char * pathname, mode_t mode)

参数说明
参数pathname指向欲建立的文件路径字符串。

下面这种open的调用方式就相当于是creat
open(const char * pathname ,(O_CREAT|O_WRONLY|O_TRUNC))

返回值
creat()会返回新的文件描述词,若有错误发生则会返回-1,并把错误代码设给errno

附加说明
creat函数的一个不足之处是它以只写方式打开所创建的文件

3.3 close

关闭一个被打开的文件。

cpp 复制代码
头文件
#include<unistd.h>

函数原型
int close(int fd);

函数说明
参数fd为先前由open()或creat()所返回的文件描述符; 
当使用完文件后若已不再需要则可使用close()关闭该文件
close()会让数据写回磁盘,并释放该文件所占用的资源。


返回值
若文件顺利关闭则返回0,发生错误时返回-1。 

附加说明
虽然在进程结束时,系统会自动关闭已打开的文件,但仍建议自行关闭文件,并检查返回值。

3.4 read

将从指定的文件描述符中读出的数据放到指定的位置,并返回实际读取的字节数。即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。

cpp 复制代码
头文件
#include<unistd.h>

定义函数
ssize_t read(int fd,void * buf ,size_t count);

函数说明
read()会把参数fd 所指的文件传送count个字节到buf指针所指的内存中。
若参数count为0,则read()不会有作用并返回0。
返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,
此外文件读写位置会随读取到的字节移动; 

返回值
成功返回读取的字节数,出错返回-1。
  • read函数实际读到的字节数少于要求读的字节数时会出现一些异常
  • 读操作完成后,文件的当前位置将从读之前的位置加上实际读的字节数
  • 当有错误发生时则返回-1,错误代码存入errno中,而文件读写位置则无法预期

3.5 write

向打开的文件写入数据。

cpp 复制代码
头文件
#include<unistd.h>

函数原型
ssize_t write (int fd,const void * buf,size_t count);

函数说明
write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。
当然,文件读写位置也会随之移动; 

返回值
如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。
write出错的原因可能是磁盘满、没有访问权限、或写超过文件长度限制等等

附加说明
将数据写入已打开的文件内。对于普通文件,写操作从文件当前位置开始写(除非打开文件时指定了
O_APPEND选项)。写操作完成后,文件的当前位置将从写之前的位置加上实际写的字节数

数据无法一次性读完时

  1. 第二次读buf中数据时,读位置指针并不会自动移动
  2. 按如下格式实现读位置移动:write(fp, p1+len, (strlen(p1)-len),直至指针恢复

Write一次可以写的最大数据范围是8192

  1. 写入数据大小最好小于buff中的值
  2. Count参数值大于SSIZE_MAX,则write调用的结果未定义
  3. Count参数值为0时,write调用会立即返回0这个值

Write调用返回时,内核已经将缓冲区所提供的数据复制到内核的缓冲区,但是无法保证数据已经写出到预定的目的地。

5.6 lseek

所有被打开的文件都会有一个文件偏移量(current file offset),通常是一个非负整数,用来表明当前文件开始处到文件当前读取位置的字节数。文件打开时,偏移量为0,除非指定了O_APPEND选项。

  • 常用来找到文件的开头、末尾,判断当前位置
  • lseek只记录当前文件的偏移量在内核中,以便下次读写时使用该偏移量,本身并不会引起任何I/O操作。
  • 偏移量可以大于文件当前的长度
cpp 复制代码
头文件
#include <sys/types.h>
#include <unistd.h>

函数原型
off_t lseek(int fd, off_t offset, int whence);

参数说明
参数fd:文件操作符
offset:偏移量
whence:文件偏移相对位置

参数 offset 的含义取决于参数 whence:
如果 whence 是 SEEK_SET,offset相对于文件开头进行偏移
如果 whence 是 SEEK_CUR,offset相对文件当前位置进行偏移
如果 whence 是 SEEK_END,offset相对于文件末尾进行偏移

返回参数:操作成功会返回文件新的偏移量,失败返回-1

可以使用下面的写法获取文件大小
int file_size = lseek(i,0,SEEK_END);

5.7 pread

在给定的偏移量读取一个文件描述符

cpp 复制代码
头文件
#include <unistd.h>

函数原型
ssize_t pread(int fd, void *buf, size_t count, off_t offset);

返回值:返回读到的字节数,出错返回-1,结尾返回0

由于lseek和read调用之间,内核可能会临时挂起进程,会导致一些同步问题,
调用pread相当于顺序lseek和read。

5.8 pwrite

在给定的偏移量写入一个文件描述符

cpp 复制代码
头文件
#include <unistd.h>

函数原型
ssize_t pwrite (int fd, const void *buf, size_t count, off_t pos );

返回值:返回已写的字节数,出错返回-1

由于lseek和write调用之间,内核可能会临时挂起进程,会导致一些同步问题,
调用pwrite相当于顺序lseek和write。
pread/pwirte和read/write的区别
  • 调用更容易
  • 完成工作后不会改变文件指针
  • 避免使用lseek时可能造成的竞争条件

5.9 ioctl

设备驱动程序中对设备的I/O通道进行管理

cpp 复制代码
头文件
#include<sys/ioctl.h>

定义函数
int ioctl(int fd, int cmd, ...);

函数说明
ioctl()能对一些特殊的文件(主要是设备)进行一些底层参数的操作。
许多字符设备都使用ioctl请求来完成对设备的控制; 

返回值
成动返回0。当有错误发生时则返回-1,错误代码存入errno中 

附加说明
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。

所谓对I/O通道进行管理,就是对设备的一些特性进行控制,
例如串口的传输波特率、马达的转速等等。

三、标准IO库

标准IO库是C当中的库函数,函数原型在文件stdio.h中定义,对底层I/O系统调用进行了封装,为程序员提供了带有格式转换功能的输入输出操作,并在用户空间增加了缓冲区管理。

  • 分离应用程序控件和实际物理设备
  • 减少直接读盘次数,提高性能
    1. 读取数据前查看数据是否已经存在于页缓存中,如果存在,数据直接从缓存返回给应用程序
    2. 写数据时先写到页缓存中

eg.缓冲区:在内存开辟一个"缓冲区",为程序中的每一个文件使用;当执行读文件的操作时,从磁盘文件将数据先读入内存"缓冲区",装满后再从内存"缓冲区"依此读出需要的数据。执行写文件的操作时,先将数据写入内存"缓冲区",待内存"缓冲区"装满后再写入文件。

1.有了系统API,为什么还要标准I/O库?

  • 直接使用API,需要考虑许多细节问题。
  • 底层系统调用函数在进行输入输出时,频繁在用户态和内核态之间切换,每次读写数据量少,频繁读写,增加系统开销。

2.常见的标准IO库函数

2.1 fopen

打开一个指定文件

cpp 复制代码
头文件
 #include <stdio.h>
 
定义函数
FILE *fopen(const char *restrict pathname, const char *restrict type);

参数说明
pathname:要打开的文件名 
type:指定文件的读、写方式
    r或rb:读
    w或wb:写,使文件长度为0
    a或ab:添加内容、在文件尾部写入
    r+或r+b或rb+:读写
    w+或w+b或wb+:读写,使文件长度为0
    a+或a+b或ab+:在文件尾部读写
    
返回值
返回一个文件流FILE
FILE 是 <stdio.h> 头文件中的一个结构体,它专门用来保存文件信息。

|----------|---|---|---|----|----|----|
| 限制 | r | w | a | r+ | w+ | a+ |
| 文件必须存在 | 1 | | | 1 | | |
| 删除文件之前内容 | | 1 | | | 1 | |
| 流可读 | 1 | | | 1 | 1 | 1 |
| 流可写 | | 1 | 1 | 1 | 1 | 1 |
| 流只能在尾部写 | | | 1 | | | 1 |

2.2 fclose

关闭一个打开的文件流。

使用fclose()函数就可以把 缓冲区内最后剩余的数据输出到内核缓冲区,并释放 文件指针和有关的缓冲区。

cpp 复制代码
头文件
 #include <stdio.h>
 
定义函数
int fclose(FILE *stream);

参数说明
stream:指定需要关闭的文件

返回值
如果流成功关闭,fclose 返回 0,否则返回EOF(-1)

2.3 fread

从文件流中读取数据

cpp 复制代码
头文件
 #include <stdio.h>
 
 定义函数
 size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
 
 参数说明
 buffer为接收数据的地址
 size为一个单元的大小
 count为单元个数
 stream为文件流。
 
 返回值
 返回实际读取的单元个数。
 如果小于count,则可能文件结束或读取出错;
 可以用ferror()检测是否读取出错,用feof()函数检测是否到达文件结尾。

 如果size或count为0,则返回0。

2.4 fwrite

向文件流中写入数据

cpp 复制代码
头文件
 #include <stdio.h>
 
 定义函数
 size_t fwrite(const void *buffer, size_t size, size_t count,
 FILE *stream);
 
参数说明
buffer为数据源地址
size为每个单元的字节数
count为单元个数
stream为文件流指针

返回值
返回成功写入的单元个数。
如果小于count,则说明发生了错误,文件流错误标志位将被设置,随后可以通过ferror()函数判断。

2.5 fgetc

从文件流读取一个字符

cpp 复制代码
头文件
 #include <stdio.h>
 
 定义函数
int fgetc(FILE * stream);

参数说明
stream:文件流

返回值
成功返回读取到的字符,读到文件结尾时返回EOF。

2.6 fgets

从文件流读取一行或指定行数的字符

cpp 复制代码
头文件
 #include <stdio.h>
 
定义函数
char * fgets(char * string, int size, FILE * stream);

参数说明:
string 一个字符数组,用来保存读取到的字符。
size 要读取的字符的个数。
    如果该行字符数大于size-1,则读到 size-1 个字符时结束,并在最后补充' \0';
    如果该行字符数小于等于 size-1,则读取所有字符,并在最后补充 '\0'。即,
    每次最多读取 size-1 个字符。
stream 文件流指针。

返回值
读取成功,返回读取到的字符串,即string;
失败或读到文件结尾返回NULL。
因此我们不能直接通过fgets()的返回值来判断函数是否是出错而终止的,
应该借助feof()函数或者ferror()函数来判断。

2.7 fputs

将指定字符串写到文件流中

cpp 复制代码
头文件
 #include <stdio.h>
 
定义函数
int fputs(char * string, FILE * stream);

参数说明
string 将要写入的字符串
stream 文件流指针

返回值
成功返回非负数,失败返回EOF

2.8 fseek

移动文件的读写指针到指定的位置

cpp 复制代码
头文件
 #include <stdio.h>
 
定义函数
int fseek(FILE * stream, long offset, int fromwhere);

参数说明
stream 文件指针
offset 偏移量
fromwhere 指针的起始位置

参数 offset 为根据参数 fromwhere 来移动读写位置的位移数。
参数 fromwhere 的值可以是下列其中一种:
SEEK_SET:从距文件开头 offset 位移量为新的读写位置;
SEEK_CUR:以目前的读写位置往后增加 offset 个位移量;
SEEK_END:将读写位置指向文件尾后再增加 offset 个位移量。

2.9 ftell

获取当前读写指针的位置

cpp 复制代码
头文件
 #include <stdio.h>
 
定义函数
long ftell(FILE * stream);

参数说明
stream  已打开的文件指针

返回值
返回当前的读写位置,失败返回 -1

2.10 rewind

将文件指针重新指向文件的开头,同时清除和文件流相关的错误和eof标记,相当于调用fseek(stream, 0, SEEK_SET)

cpp 复制代码
头文件
 #include <stdio.h>
 
定义函数
void rewind(FILE * stream);

参数说明
stream 打开文件的指针
相关推荐
OpenPomeloxCommunity1 小时前
3. Linux 同步机制之 spinlock 设计与实现(一):通用层
linux
念恒123062 小时前
网络基础
linux·网络·c++
皮卡狮3 小时前
Linux静态库和动态库详解
linux
撩得Android一次心动3 小时前
Linux编程笔记4【个人用】
linux·笔记·学习
七牛云行业应用4 小时前
Ollama 本地部署 DeepSeek 完全指南:macOS / Windows / Linux 三端安装 + GPU 配置 + API 调用
linux·windows·macos
顧棟4 小时前
Zookeeper热缩容解析
linux·服务器·zookeeper
xqqxqxxq4 小时前
技术笔记:上下文工程(Context Engineering)心得总结(李博杰《深入理解 AI Agent》2.1,2.2观后总结)
人工智能·笔记
三言老师5 小时前
CentOS7.9:Redis服务器部署结构化实战教程
linux·运维·服务器·数据库