1. 在Linux系统的/etc目录下有这样一个文件passwd,该文件里包含系统中所有用户信息的记录,记录里包含每个用户的如下信息:用户名、密码、用户ID、组ID、用户全名、用户主目录和用户登录所用的shell,可以分析这个文件得到所有普通用户(用户ID有1000及以上的)的基本信息:用户名、所在的组及用户的主目录。请设计一个脚本(shell)程序,通过菜单的选择可以增加、删除、查找和显示特定用户信息的功能,可以查看有多少个组、对应某个组内的用户。
代码:
$A:调用变量A,输出A的值
menu.sh
#! /bin/bash(这是一个shell脚本)
record_file=/etc/passwd
add_file=add.sh(别名-名称)
delete_file=delete.sh(将文件定义为变量,引用这个变量即为引用这个文件)
while true
do
clear
echo " 主菜单 "
echo "*********************************************"
echo "1、增加用户 "
echo "2、删除用户 "
echo "3、查询用户 "
echo "4、显示所有用户信息 "
echo "5、显示用户名和用户ID "
echo "输入0退出系统 "
echo -n "你的选择是:"
read choice
case $choice in
1)sh $add_file;;
2)sh $delete_file;;
3)echo -n "输入用户名:" //查询用户
read name
while test -z $name (test-z 字符串:字符串的长度为零)
do
echo "没有输入用户名"
echo -n "输入用户名:"(echo -n:不换行输出)
read name
done //获取用户输入的字符串,并遍历record_file是否有与其相同的字符串
if( cut -f 1 -d: $record_file | grep -iq ^$name )(cut -f与-d一起使用指定分隔后一列,
grep-i显示匹配行,grep-q找到匹配行,但不显示,grep-iq查找name中的字符串)
then(|:管道符,可理解为前一个命令的输出,作为后一个命令的输入)
grep -iw ^$name $record_file(^:匹配正则表达式的开始行)
else
echo "没有找到$name"
fi;;
4)cat $record_file | tr ":" " " ;; //显示所有用户信息
5)cat $record_file | tr ":" "," | cut -f 1,3 -d,;;(cut-f:与-d一起使用,指定显示哪个区域,1,3字段分别为用户名和用户ID) //显示用户名和用户ID
0)break 10;;
*)echo -n "无效的代码"
esac
if test "$choice" = "1" -o "$choice" = "2";then(test表达式1-o表达式2: 两个表达式有一个为真)
break;
fi
echo -n "按回车键继续。。"
read answer
done
add.sh
#! /bin/bash
record_file=/etc/passwd
while true
do
clear
echo "输入新用户信息!"
echo " "
#用户名
while true
do
echo -n "用户名:"
read name
if test -z $name(test-z 字符串:字符串的长度为零)
then
echo "没有输入用户名"
continue
fi
if test $name = "N" -o $name = "n"(test表达式1-o表达式2: 两个表达式有一个为真)
then
sh menu.sh
exit
fi //保证用户名只包含字母和数字,并且要以字母开头
if (`echo $name |grep -q '^[A-Za-z0-9]*$'` && `echo $name | grep -q '^[A-Za-z]'` )(`命令`:反引号里面可以运行命令grep-q:搜索name中是否有匹配的内容)
then
if (grep -wq ^$name $record_file )//在etc/passwd下查找是否有与字符串name相同的。
then
echo "该用户名已存在,请重新输入!"
else
break
fi
else
echo "用户名只包含字母和数字,并且要以字母开头"
continue
fi
done
#密码
while true
do
echo -n "密码:"
read pwd1
echo
if test -z $pwd1
then
echo "密码不能为空!请重新输入!"
continue
fi
if test $pwd1 = "N" -o $pwd1 = "n"//test A -o B两个表达式中有一个为真
then
sh menu.sh
exit
fi
if test `expr length $pwd1` -ge 6(test 数字1 -ge 数字2:数字1大于数字2;expr length字串:返回字符串长度) //确保密码长度至少要6位
then
break
else
echo "密码长度至少要6位!请重新输入!"
fi
done
#用户ID
while true
do
echo -n "用户ID:"
read id
if test -z $id
then
echo "没有键入ID"
continue
fi
if test $id = "N" -o $id = "n"
then
sh menu.sh
exit
fi
if (`echo $id | grep -q '^[0-9]*$'` && test $id -ge 500 -a $id -le 60000)(test num1 -ge num2:num1大于等于num2;test num1-le num2:num1小于等于num2;test表达式1 -a表达式2:两个表达式都为真)
then
if (cut -f 3 -d : $record_file| grep -w $id)//cut-f:按照字段选取;-d用于设置间隔符号
then
echo "ID已存在,请键入别的ID"
continue
else
break
fi
else
echo "无效ID"
continue
fi
done
#组ID
while true
do
echo -n "组ID:"
read gid
if test -z $gid
then
echo "没有键入组ID"
fi
if test $gid = "N" -o $gid = "n"
then
sh menu.sh
exit
fi
if (`echo $gid | grep -q '^[0-9]*$'` && test $gid -ge 500 -a $gid -le 60000)//有效ID的条件:0~9
then //500<ID<60000
break
else
echo "无效组ID"
continue
fi
done
#主目录
echo 主目录:$name
#登录shell
while true
do
echo -n 登录shell:
read she
if test -z $she
then
echo "没有输入!"
continue
fi
if test $she = "N" -o $she = "n"
then
sh menu.sh
exit
fi
if test $she = 'bash' -o $she = 'sh' -o $she = 'kash' -o $she = 'csh' //系统内的有效shell名
then
break
else
echo "输入的shell名不在本系统范围内!"
continue
fi
done
#用户全名
while true
do
echo -n "用户全名:"
read fullname
if test -z $fullname
then
echo "没有输入!"
continue
fi
if test $fullname = "N" -o $fullname = "n"
then
sh menu.sh
exit
fi
break
done
#主目录
echo 主目录:$name
#添加用户
echo $name:$pwd1:$id:$gid:$fullname:/home/$name:/bin/$she >> $record_file;
echo "保存成功!"
echo "$name $pwd1 $id $gid $fullname /home/$name /bin/$she"
echo "按回车继续.."
read answer
sh menu.sh
done
delete.sh
#! /bin/bash
record_file=/etc/passwd
clear
echo "删除用户"
while true
do
echo -n "输入用户ID:"
read uid
if test -z $uid
then
echo "没有输入!"
continue
fi
if test $uid = "N" -o $uid = "n"
then
sh menu.sh
exit
fi
if test $uid -ge 500 -a $uid -le 60000
then
name=`awk -F: '$3=="'$uid'"' $record_file | cut -f 1 -d:`//awk完成打开文件,读取文件行,进行相应处理,关闭文件;awk[-F 域分隔符]awk程序段输入文件。
if (! test -z $name )
then
awk -F: '$3=="'$uid'"' $record_file//$3 当前程序的第3个参数
userdel -r $name//通过userdel-r删除$name
name=`awk -F: '$3!="'$uid'" {print $0}' $record_file`//$0:当前程序的名称
echo "记录删除"
else
echo "没有找到$uid"
fi
else
echo "无效的用户ID"
continue
fi
echo "按回车继续.."
read answer
sh menu.sh
exit
done
运行的截图:shell的两种运行方式1. /bin/sh xxxx.sh 2. chmod o+x xxxxx.sh ./xxxxx.sh
主菜单
总结:(Shell的理解-200字左右)
Shell是操作系统的最外层,Shell的翻译为中文意思是“壳”,它是相对于内核来说的,它展现在内核外面,为用户提供一个界面,用户可以通过这个界面访问操作系统内核的服务。Shell通过提示用户输入,向操作系统解释该输入,然后处理来自操作系统的任何结果输出来管理用户与操作系统之间的交互。所以,shell是用户与Linux操作系统之间沟通的桥梁,用户可以输入命令执行,又可以利用shell脚本(Linux命令的总和的文件)去运行。Linux shell种类非常多,最常用的是shell是bash(Bourne Again Shell),bash是环境,脚本是命令,命令通过环境解析给系统,系统就会输出东西给我们,Bash也是大多数Linux系统默认的shell。
2.编写C程序,创建源文件:hello.c max.c min.c main.c 和my.h,在hello.c中定义一个函数sayhello(char *s),打印“你好:s”,min.c中求最小值函数定义min(int a,int b),max.c中求最大值函数定义max(int a,int b),my.h中写入三个函数的声明。在main.c中调用这个几函数,输入自己的姓名和学号,调用sayhello函数显示信息“你好:姓名-学号”。输出读入两个数,调用min和max,输出最大值和最小值。请创建Makefile文件,用make命令生成可执行文件,可执行的文件名为自己的姓名+学号。
代码:
main.c
#include<stdio.h>
#include<string.h>
#include"my.h"
int main()
{
char num[20];
char name[20];
printf("请输入你的学号和姓名:");
scanf("%s %s", num, name);
strcat(num, "-");//strcat函数将name字符串加到num结尾处
strcat(num, name);
sayhello(num);
printf("请输入两个数:");
int a, b;
scanf("%d %d",&a,&b);
printf("最大值:%d\n",max(a,b));
printf("最小值:%d\n",min(a,b));
}
hello.c
#include<stdio.h>
void sayhello(char *s)
{
printf("你好:%s\n",s);
}
max.c
int max(int a, int b)
{
return a > b ? a : b;
}
min.c
int min(int a, int b)
{
return a > b ? b : a;
}
my.h
void sayhello(char *s);
int min(int a, int b);
int max(int a, int b);
Makefile//这个Makefile将main.c、hello.c、max.c、min.c这四个文件编译成对应的.o文件,然后将这四个.o文件编译成名叫out的可执行程序,经过预处理-编译-汇编-链接。
2019040269-曾子安:main.o hello.o max.o min.o
gcc -o 2019040269-曾子安main.o hello.o max.o min.o
main.o:main.c
gcc -c -o main.o main.c
hello.o:hello.c
gcc -c -o hello.o hello.c
max.o:max.c
gcc -c -o max.o max.c
min.o:min.c
gcc -c -o min.o min.c
运行的截图:
总结:makefile带来“自动化编译”,执行make命令,扫描当前目录下的Makefile文件,在 Makefile 文件中描述了所有文件的编译顺序、编译规则,预处理-编译-汇编-链接。Makefile文件要写好依赖关系,继续为这些依赖扫描 Makefile 建立其依赖关系,然后编译它们。一旦主依赖编译之后,然后就编译主目标。程序就通过这样编译main.c、hello.c、max.c、min.c,并生成2019040269-曾子安这个可执行文件,执行该可执行文件就实现题目的要求。
3.读取“/etc”目录下所有的目录结构,不显示.和..目录,并按字母递增顺序排列,按目录级别缩进显示。
代码:
etc.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<string.h>
#include<pwd.h>
#include<grp.h>
#include<time.h>
#include<dirent.h>
char file_type(int mode);
int print_filename(struct stat file, char *filename);
int list_file(struct stat file, char *filename, int deep);
int list_dir(char *dirname, int deep);
int main(int argc, char *argv[]){ //argc表示argv中存放string的个数
char path[1024]={}; //默认的argc=1,因为argv[0]就是当前程序的路径
struct stat filepath; //利用stat结构体存储文件的信息
char = "/etc";
if(lstat(srcpath, &filepath) == -1){
perror(srcpath);
exit(1); //文件异常时,exit(1)退出,返回给操作系统
}
if(S_ISDIR(filepath.st_mode)){ //通过st_mode这个变量来判断文件类型
list_dir(srcpath,0); //调用函数list_dir;文件是DIR
}else{
list_file(filepath,srcpath,0); //调用函数list_file;
}
return 0;
}
//递归的判断了一下文件是目录还是文件,然后分别调用list_dir和list_file函数。
char file_type(int mode){ //判断文件类型
if(S_ISDIR(mode)){ //linux下的七种文件类型,
return 'd'; //d目录
}
if(S_ISREG(mode)){ //-普通文件
return '-';
}
if(S_ISFIFO(mode)){ //p命名管道(FIFO)文件
return 'p';
}
if(S_ISLNK(mode)){ //l 符号连接文件
return 'l';
}
if(S_ISBLK(mode)){ //b 块设备文件
return 'b';
}
if(S_ISSOCK(mode)){ //s socket文件
return 's';
}
if(S_ISCHR(mode)){ //c 字符设备文件
return 'c';
}
return '0';
}
int print_filename(struct stat file, char *filename){ //输出文件名
char filename_ink[128]={};
memset(filename_ink, '\0', 128); //memset(指针或数组,第一个参数的值,第一个参数的长度)函数:对较大的结构体或数组进行清零操作的一种最快的方法。
if(S_ISDIR(file.st_mode)){ //设置目录的首字母与剩余字母为不同颜色
printf("\033[34m%s\033[0m\n", filename);
return 0;
}
printf("\033[30m%s\033[0m\n", filename);
return 0;
}
int list_file(struct stat file, char *filename, int deep){ //显示文件函数
printf("%*s%c", deep, " ",file_type(file.st_mode));
print_filename(file,filename);
return 0;
}
int list_dir(char *dirname, int deep){ //显示目录函数
struct dirent **dir;
struct stat file;
int n = 0;
n = scandir(dirname, &dir, 0, alphasort); //调用scandir函数进行排序。
chdir(dirname);
while(n--){
if(lstat(dir[n]->d_name, &file) != 0){ //通过循环,指针的改变实现排序
perror(dir[n]->d_name);
continue;
}
if(S_ISDIR(file.st_mode)){
if(dir[n]->d_name[0] == '.'){ //遍历的时候跳过 “.”和“..”。
continue;
}
list_file(file, dir[n]->d_name, deep);
list_dir(dir[n]->d_name, deep+4); //按目录级别缩进
chdir("..");
free(dir[n]);
}else{
list_file(file, dir[n]->d_name, deep);
free(dir[n]);
}
}
free(dir);
return 0;
}
运行的截图:
总结:本题用到了st_mode来判断文件类型,linux中stat()函数可以获取文件的状态,执行成功返回0,失败返回-1,取得的文件状态存放在buf指针指向的struct stat结构体中,st_mode这个变量为该结构体中的一个变量,st_mode是用特征位(如S_IFDIR 0040000 目录)来表示文件类型的;判断文件类型时,st_mode的值与上面给出的标志位相与,结果与标志位比较。利用stat结构体存储文件的信息,遍历的时候跳过 “.”和“..”然调用scandir函数进行排序。递归的判断了一下文件是目录还是文件,然后分别调用list_dir和list_file函数就可以了。
4.实现mycp功能,功能需求如下:
cp dir1/a.doc dir2 表示将dir1下的a.doc文件复制到dir2目录下
// An highlighted block
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main(char argc,char *argv[])
{
//判端参数是否正确,否则退出
if(argc!=3)
{
printf("./a.out file_name1 file_name2\n");
return -1;
}
int fd_src;//源文件
int fd_aim;//复制到的目标文件
//打开源文件,打开读取权限,准备拷贝
fd_src=open(argv[1],O_RDONLY);
//判断源文件是否打开成功
if(fd_src<0)
{
printf("file_name1 open fail! ");
return -2;
}
//打开目标文件,O_TRUNC:若文件存在,直接覆盖,否则创建(O_CREAT),开启读写权限
fd_aim=open(argv[2],O_WRONLY|O_TRUNC|O_CREAT,0666);
char buf[256];//文件存储缓冲区
int ret; //读取文件内容长度
while(1)
{ //对于源文件:用read函数把内容全部读取到缓存中
ret=read(fd_src,buf,sizeof(buf)); //实现一个cp命令其实就是读写文件的操作
write(fd_aim,buf,ret);
//对于目标文件:用write函数把缓存中的全部内容写入目标文件
//判断是否文件读取结束
if(ret!=256)
break;
}
//关闭打开的源文件和目标文件
close(fd_src);
close(fd_aim);
return 0;
}
cp -r dir1 dir2 表示将dir1及其dir1下所包含的文件复制到dir2下
/*copy.h*/
#ifndef _COPY_H_
#define _COPY_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
/*复制文件时的读写缓冲区大小*/
#define BUF_SIZE 8092
/*保存文件路径的缓冲区大小*/
#define PATH_SIZE 1024
/*文件权限*/
#define FILE_MODE 0664
/*目录权限*/
#define DIR_MODE 0664
void sys_err(const char *); /*errno错误处理*/
void deal_dir(const char *,const char *); /*递归函数,处理目录*/
void deal_copy(const char *,const char *); /*处理绝对路径的文件*/
void read_write(void); /*拷贝文件时的读写操作*/
void mkdir_newdir(const char *); /*为目标创建空目录*/
void get_allpath(char *,char *,const char *,const char *); /*获取source与target的绝对路径*/
void init_allpath(char *,char *,char *,char *,const char *); /*清空存储路径与文件名的缓冲区并重新赋值*/
#endif
/*copy.c*/
#include<copy.h>
/*
*将读source与写target的文件描述读定义成全局变量
*fd[0] == fd_read
*fd[1] == fd_write
*/
int fd[2] = {0};
void sys_err(const char * ptr)
{
perror(ptr);
exit(EXIT_FAILURE);
}
void deal_dir(const char *old_path,const char * new_path)
{
DIR * ret_opdir = opendir(old_path);//打开目录
if(ret_opdir == NULL)
sys_err("opendir");
struct dirent * ret_redir;
/*定义缓冲区存放绝对路径,eg:/home/krj/test/*/
char buf_old[PATH_SIZE] = {};
char buf_new[PATH_SIZE] = {};
/*存放绝对路径+文件名,eg:/home/krj/test/test.txt*/
char buf_oldfile[PATH_SIZE] = {};
char buf_newfile[PATH_SIZE] = {};
/*获取源文件完整路径与目标位置完整路径,分别存入buf_old与buf_new中*/
get_allpath(buf_old, buf_new, old_path, new_path);
while((ret_redir = readdir(ret_opdir))){//读取目录,失败或目录读完返回NULL
if(!strcmp(ret_redir->d_name, ".") || !strcmp(ret_redir->d_name, ".."))
continue;
else{
/*清除上次复制的文件路径,并填入新读取到的文件的路径*/
init_allpath(buf_old, buf_new, buf_oldfile, buf_newfile, ret_redir->d_name);
if((ret_redir->d_type & DT_REG) == DT_REG)//如果读到的文件是普通文件则复制
deal_copy(buf_oldfile, buf_newfile);
else if((ret_redir->d_type & DT_DIR) == DT_DIR)//如果读到的是目录则递归处理目录
deal_dir(buf_oldfile, buf_newfile);
}
}
}
void get_allpath(char * buf_old, char * buf_new, const char * old_path, const char * new_path)
{
char buf[PATH_SIZE] = {};/*存放当前路径*/
getcwd(buf,PATH_SIZE);/*获取当前路径*/
chdir(old_path);/*改变路径到source*/
getcwd(buf_old,PATH_SIZE);/*获取source的绝对路径*/
strcat(buf_old, "/");/*加上“/”以方便之后向buf_oldfile中补上具体文件名*/
chdir(buf);/*回到原来目录*/
/*与获取source绝对路径相同,获取target绝对路径,当然两次获取可以简化成一个函数,由于代码不是太多就没过多简化*/
mkdir_newdir(new_path);//先创建一个空目录
chdir(new_path);
getcwd(buf_new, PATH_SIZE);
strcat(buf_new,"/");
}
void init_allpath(char * buf_old, char * buf_new, char * buf_oldfile, char * buf_newfile, const char * name)
{
/*清空source与target的带文件名的绝对路径缓冲区*/
memset(buf_oldfile, 0, PATH_SIZE);
memset(buf_newfile, 0, PATH_SIZE);
/*将source的缓冲区更新为最新读到的source文件*/
strcpy(buf_oldfile, buf_old);
strcat(buf_oldfile, name);
/*同上理*/
strcpy(buf_newfile, buf_new);
strcat(buf_newfile, name);
}
void mkdir_newdir(const char * new_path)
{
/*目录不存在返回NULL,则创建空目录*/
DIR * ret_opdir = opendir(new_path);
if(ret_opdir == NULL){
int ret_mkdir = mkdir(new_path, DIR_MODE);/*创建子目录*/
if(ret_mkdir == -1)
sys_err("mkdir newdir");
}
}
void deal_copy(const char *old_file,const char * pathname)
{
struct stat get_message;
int ret_stat = stat(pathname, &get_message);
if(ret_stat == -1 && errno != ENOENT)//文件信息读取失败,并且不是因为无该文件造成的
sys_err("stat in copy.c of deal_copy");
fd[0] = open(old_file, O_RDONLY);/*打开source文件*/
if(fd[0] == -1)
sys_err("open oldfile in copy_file");
fd[1] = open(pathname, O_CREAT | O_TRUNC | O_RDWR, FILE_MODE);/*创建并打开target文件*/
if(fd[1] == -1)
sys_err("open newfile in copy_file");
read_write();/*进行source的读与对应的target的写(复制)*/
close(fd[0]);
close(fd[1]);
}
void read_write(void)
{
char buf[BUF_SIZE] = {};
int ret_read, ret_write;
while((ret_read = read(fd[0], buf, BUF_SIZE)) > 0){/*读到EOF时返回0结束*/
ret_write = write(fd[1], buf, strlen(buf));
/*等效于ret_write = write(fd[1], buf, ret_read);*/
if(ret_write == -1)
sys_err("write newfile");
}
}
main.c
#include<copy.h>
int main(int argc, char ** argv){
if(argc != 4){
printf("too few or lost parameter!\n");
exit(EXIT_FAILURE);
}
else if(!strcmp(argv[1], "-r") || !strcmp(argv[1], "-R")){
struct stat get_message;
int ret_stat = stat(argv[2],&get_message);
if(ret_stat == -1)
sys_err("stat argv[2]");
if(S_ISDIR(get_message.st_mode))
deal_dir(argv[2],argv[3]);//如果argv[2]是目录则按目录递归处理
else{//否则按照文件直接复制,局限性:复制单个文件时只能向当前目录复制
if(strcmp(argv[2],argv[3]) == 0){
printf("The same filename of parameter is error!\n");
exit(EXIT_FAILURE);
}
deal_copy(argv[2],argv[3]);
}
}
return 0;
}
cp -l dir1 dir2 建立dir1硬连接(hard link)的连接文件dir2,而非复制文件本身
cp -s dir1 dir2复制dir1成符号连接文件(symbolic link) dir2,即“快捷方式”文件
cp -u dir1 dir2:若目标文件dir2比源文件dir1旧,更新目标文件
代码:p
XXX.c
总结:通过argc与argv获取命令行参数将它传递给mycp命令,例如:如果用户输入的命令行是ls -l remind.c,在这里argc(参数个数)是3,argv(参数指针),argv[0](program name)argv[1](-l \0)argv[2](remind.c \0),命令行的信息对所有的程序都是可达的,不仅仅是操作系统的命令,如果要获得命令行参数,main中必须包含argc,argv这两个参数int main(int argc,char *argv[]),在c的标准化中,叫做程序参数。
5.实现mychmod功能,功能需求如下:
chmod mode file
mode要求:
u 表示该文件的拥有者,g 表示与该文件的拥有者属于同一个群体(group)者,o 表示其他以外的人,a 表示这三者皆是。
+ 表示增加权限、- 表示取消权限、= 表示唯一设定权限。
r 表示可读取,w 表示可写入,x 表示可执行,X 表示只有当该文件是个子目录或者该文件已经被设定过为可执行。
可以使用八进制数来指定权限。文件或目录的权限位是由9个权限位来控制,每三位为一组,它们分别是文件所有者(User)的读、写、执行,用户组(Group)的读、写、执行以及其它用户(Other)的读、写、执行。
代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
void get_mode(int u, int g, int o, int r, int w, int x,
int sign, mode_t *mode)
{
switch(sign) //添加权限操作是如果有这个权限的话,
//用或运算就可以实现这个效果0|1=1,1|1=1。
{
case '+': //添加权限操作
if(u) //如果u需要被写入权限
{
if(r) //如果写入的权限是r
*mode |= S_IRUSR; //S_IRUSR 00400权限 代表该文件所有者具有可读取的权限
if(w) //如果写入的权限是w
*mode |= S_IWUSR;
if(x) //如果写入的权限是x
*mode |= S_IXUSR;
}
if(g) //如果g需要被写入权限
{
if(r)
*mode |= S_IRGRP; //S_IRGRP 00040权限 代表该文件用户组具有可读取的权限
if(w)
*mode |= S_IWGRP;
if(x)
*mode |= S_IXGRP;
}
if(o) //如果o需要被写入权限
{
if(r)
*mode |= S_IROTH; //S_IROTH 00004权限 代表其他用户具有可读取的权限
if(w)
*mode |= S_IWOTH;
if(x)
*mode |= S_IXOTH;
}
break;
case '-': //删除权限操作,就是屏蔽要删除的权限
//用与运算+反码就可以实现这个效果,原理是0&任何数=0
//例如我们要删除u的x权限,u的x权限的宏S_IXUSR是八进制00100(只看后三位)100转化为二进制后取反为110111111(677),在没有chmod u-x之前权限r-xrwxrwx(577),
现在577&677(101111111&110111111=100111111=477r--rwxrwx)
这里实现了u的x权限屏蔽
if(u)
{
if(r)
*mode &= ~S_IRUSR;
if(w)
*mode &= ~S_IWUSR;
if(x)
*mode &= ~S_IXUSR;
}
if(g)
{
if(r)
*mode &= ~S_IRGRP;
if(w)
*mode &= ~S_IWGRP;
if(x)
*mode &= ~S_IXGRP;
}
if(o)
{
if(r)
*mode &= ~S_IROTH;
if(w)
*mode &= ~S_IWOTH;
if(x)
*mode &= ~S_IXOTH;
}
break;
case '=': //设置权限操作
//=的操作是一个屏蔽权限的操作,如rwxrwxrwx执行chmod u=x就变成了—xrwxrwx,这里我们的操作是将需要覆盖权限的组的rwx全部置0,然后加上需要加入的权限
if(u)
{
*mode &= (S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH); //全部置零
if(r)
*mode |= S_IRUSR; //加入权值
if(w)
*mode |= S_IWUSR;
if(x)
*mode |= S_IXUSR;
}
if(g)
{
*mode &= (S_IRUSR|S_IWUSR|S_IXUSR|S_IROTH|S_IWOTH|S_IXOTH);
if(r)
*mode |= S_IRGRP;
if(w)
*mode |= S_IWGRP;
if(x)
*mode |= S_IXGRP;
}
if(o)
{
*mode &= (S_IRGRP|S_IWGRP|S_IXGRP|S_IRUSR|S_IWUSR|S_IXUSR);
if(r)
*mode |= S_IROTH;
if(w)
*mode |= S_IWOTH;
if(x)
*mode |= S_IXOTH;
}
break;
default:
printf("sign error\n");
break;
}
}
int main(int argc, char *argv[])
{
int i;
int k;
mode_t mode; //总权限
int mode_u; //所有者权限
int mode_g; //组所属权限
int mode_o; //其他人权限
int flag_u; //标记所有者是否需要设置
int flag_g; //标记组所属是否需要设置
int flag_o; //标记其他人是否需要设置
int r;
int w;
int x;
int sign; //标记+, -, =
struct stat buf; //lstat();
//检查命令行是否输入错误
if(argc < 3)
{
printf("%s <mode> <target file>\n", argv[0]);
exit(0);
}
//有可能是多文件一起使用chmod, 所以用循环执行
for(k = 2; k < argc; k++)
{
//区分出是数字格式, 还是字符串格式
if(argv[1][0] >= '0' && argv[1][0] <= '7') //数字格式
{
mode = (unsigned)atoi(argv[1]); //字符串转换成无符号整型, mode_t是无符号类型
if(mode > 777 || mode < 0) //设置权限出错的话
{
printf("mode num error!\n");
exit(0);
}
//转换成八进制
mode_u = mode / 100; //文件所有者权限
mode_g = (mode - (mode_u*100)) / 10; //所属组权限
mode_o = mode - (mode_u*100) - mode_g*10; //其他人权限
mode = mode_u*8*8 + mode_g*8 + mode_o; //转换成八进制
}
else
{
i = 0;
//获取文件本身的权限
if(lstat(argv[k], &buf) == -1)
{
fprintf(stderr, "line %d ", __LINE__);
perror("lstat");
exit(1);
}
mode = buf.st_mode;
while(argv[1][i] != '\0')
{
r = w = x = 0;
flag_u = flag_g = flag_o = 0;
while(argv[1][i] != ',' && argv[1][i] != '\0')
{
if(argv[1][i] == 'u')
flag_u = 1;
else if(argv[1][i] == 'g')
flag_g = 1;
else if(argv[1][i] == 'o')
flag_o = 1;
else if(argv[1][i] == 'a')
flag_u = flag_g = flag_o = 1;
else if(argv[1][i] == '+')
sign = '+';
else if(argv[1][i] == '-')
sign = '-';
else if(argv[1][i] == '=')
sign = '=';
else if(argv[1][i] == 'r')
r = 'r';
else if(argv[1][i] == 'w')
w = 'w';
else if(argv[1][i] == 'x')
x = 'x';
i++;
}
get_mode(flag_u, flag_g, flag_o, r, w, x, sign, &mode);
if(argv[1][i] == ',')
i++;
}
}
if( chmod(argv[k], mode) == -1)
{
perror("chmod error");
exit(1);
}
}
return 0;
}
运行的截图:
、
总结:linux中,chmod这个命令的功能是改变文件的权限,用c语言实现这个功能的核心就是利用chmod()这个函数和对需要改变的权限的分析。
chmod命令有2个格式(1) chmod 777 test.c//数字格式(2)chmod a+x, u+rw test.c //字符格式,分别处理它们:
"数字格式","数字(即777)"是需要写入的权限,因为"数字"是中命令行获取的, 而从命令行获取的信息都是字符串的形式存在的, 所以我们需要将数字(字符串类型), 转换成整型,所以需要用到atoi()函数(atoi()函数功能: 将字符串转换成整型), 执行了mode = atoi("数字")后, mode现在是一个整型了,因为在文件的权限中有3中权限r,(可读) w(可写), x(可执行), 分别对应r = 4, w = 2, x = 1. 。即如果满权限的话就是r+w+x = 7, 无权限的话就是0, 因为文件有3个组, 文件所有者, 组所属, 其他人。 所以最大的权限为777, 最小为000.
因为atoi()函数返回的类型是一个整型数, 即一个十进制。
而chmod(const char *path, mode_t mode)中mode需要的数是一个八进制的数,所以我们现在要将mode转换成一个八进制。
chmod a+x, u+rw test.c//字符格式。 在chmod命令中 +代表添加权限, -代表删除权限, =代表设置权限。例如 我们有一个文件test.c 它的权限是 rwxrw-rw-,现在我们用chmod u=x test.c 后, 这个文件的权限就变成了--xrw-rw, u=x代表中文件所有者的这个组中, 除了x(可执行)其他权限都被覆盖,对字符格式的分析, 更多的是分析+,--,=这些操作。