通用许可证(GPL)条款下发布的一些主要GNU项目软件:
通过以上一些列的可用的自由软件+Linux kernel,可以说:创建一个GNU的、自由的类UNIX系统的目标已经通过Linux系统实现了。
Linux内核+一系列工具程序=Linux发行版
POSIX:
POSIX(Portable, Operating System Interface)是基于UNIX或类UNIX操作系统的一系列操作系统接口标准。标准定义了常用接口(open, write…)和通用工具(cd, ls…)。POSIX在源代码级别支持应用程序的可移植性,因此应用程序可以构建为在任何符合POSIX的操作系统上运行。
echo $PATH
可选的操作系统组件和第三方应用程序可能被安装在 /opt 目录下
shell是一个普通的用户程序,它解析命令行中的字符串并执行命令。shell是用户程序,并非内核的一部分。经典的shell是 bash,一般Linux都会预装。csh也是个命令解释器。
ls > out.txt # 重定向,覆盖文件
ls >> out.txt # 重定向,追加
# 将标输出误和标准错误分别重定向到不同的文件中
ls > out.txt 2> out2.txt
# 如果想把两组输出都重定向到一个文件中,可以用>&操作符来结合两个输出
# 将1,2都重定向到同一个 out.txt 需注意操作符出现的顺序。
# “将标准错误重定向到与标准输出相同的地方”
ls > out.txt 2>&1 #
# Linux通用“回收站” /dev/null 将所有输出丢弃
ls > /dev/null 2>&1
查看系统中运行的所有进程的名字,但包括shelll本身
ps -xo comm|sort |uniq | grep -v sh | less
# sort按照字母顺序排序,uniq:去除同名的 grep -v sh删除名为sh的进程
bash通配符:
#!/bin/bash
#!/bin/env python 是更为通用的写法
for i in *
do
if grep -q POSIX $i
then
echo $file
fi
done
exit 0 # optional exit normal
chmod +x test.bin # 所有用户都有该文件的可执行权限
a=a # 1.等号两边不能有空格
b='a b' #2.有空格必须用引号
echo $b $0 $1 ...
read val # 等待用户输入字符,直到遇到... you known what that mean
echo $val
echo '$val'
echo "$val"
echo \$val
单引号不会将$b替换为它的值,双引号就会
转移字符 \ 这shell中也有效
检查一个文件是否存在
if [ -f file.c ]; then # [ 命令前后要空格 ( [ 相当于 test 关键字), 这样写then前要加 ;
# 结果为bool形
...
fi
# or
if [ -f file.c ]
then
...
fi
# or
if test -f file.c
then
...
fi
test命令可使用的条件类型可归为3类:字符串比较、算术比较和与文件有关的条件测试:
echo -n agbc # 去除换行符
read timeofday
if [ "$timeofday" = "yes" ]; then
echo "yes"
elif [ "$timeofday" = "no" ]; then
echo "no"
else
echo "???"
exit 1
fi
exit 0
for
for i in a b cd e
do
echo $i
done
# or
for i in a b cd; do
echo $i
done
# $(command)语法
for file in $(ls f*.sh); do
lpr $file
done
while
while condition do
...
done
# or
while [ -d LOGS ] ;do
echo "ab"
sleep 1
done
until
until condition # 循环将反复执行直到条件为真
do
...
done
#---------------
until who | grep "$1" > /dev/null
do
sleep 60
done
# ring the bell
echo -e '\a'
case
case var in
pattern [ | pattern] ...) statements;;
pattern [ | pattern] ...) statements;;
esac
#-------------------
read timeofday
case "$timeofday" in
yes) echo "sdf";;
no ) echo sd;;
n ) echo sdf;;
* ) echo sjdlfkj;;
esac
#-------------------
case "$timeofday" in
yes | y | Yes | YES ) echo "slkdjf";;
n* | N* ) echo asdf;;
* ) echo jsdlkfj;;
esac
#-------------------
case "$timeofday" in
yes | y | Yes | YES)
echo askjdf
echo sjldkfj
;;
[nN]*)
echo sjldkfjsdfsdf
echo sjldkfjlksjdfljsldkfjlsdjf
;;
[Yy] | [Yy][Ee][Ss])
echo haha
;;
*)
echo j
echo sdl
exit 1
;;
esac
命令列表 逻辑结构
statement1 && statement2 && statement3 && …
&&的作用是检查前一条命令的返回值 左边为true才执行右边
statement1 || statement2 || statement3 || …
执行一系列命令直到有一条命令成功
|| 只有左边为false才执行右边,遇到true立即停止
if [ -f file1 ] && echo "hello you" && [ -f file2] && echo "hehe"
then
echo "in if"
else
echo "in else"
fi
#--------------------
if [ -f file1 ] || echo "hello you" || [ -f file2] || echo "hehe"
then
echo "in if"
else
echo "in else"
fi
#-------------------
[ -f file1 ] && command for true || command for false
语句块
将多条语句当成一条
get_confirm && {
grep -v "$cdcatnum" &tracks_file > $temp_file
cat $temp_file > $tracks_file
echo
add_record_tracks
}
function_name(){
statements
}
#------------
foo(){
echo fucntion too is executing
}
echo script starting
foo
echo script ended
sleep 1
exit 0
函数的用法
#!/bin/bash
#!/bin/env python 是更为通用的写法
yes_or_no(){
echo "is your name $@"
while true
do
echo -n "Enter yes or no: "
read x
case "$x" in
[Yy] | [Yy]es ) return0;;
[Nn] | [Nn]o ) return1;;
*) echo "Answer yes or no"
esac
done
}
echo "Original parameter are $*"
if yes_or_no "$1"
then
echo "Hi $1, nice name"
else
echo Never mind
fi
exit 0
break
for dir in fred*
do
if [ -d "$dir" ]; then
break;
fi
done
echo first directory starting fred was $dir
: 命令
冒号(:)命令是一个空命令。偶尔用于简化 条件逻辑,相当于true的一个别名。由于它是内置命令,所以它运行的比true快。
while:
相当于while true
无限循环
if [ -f fred ]; then
:
else
echo file nnooo
fi
continue
类似C
.命令
source命令和.命令差不多是同义词
source ./shell_script . ./shell_script
通常当前shell在执行命令是会fork然后waiti子进程执行完毕,而.命令是不让shell fork
echo
echo -n "结尾没有换行"
echo -e "转移字符有效\t\a\n等"
eval
对参数求值。有点像一个额外的$,它给出一个变量的值
foo=10
x=foo
y='$'$x #输出:$foo
echo $y
foo=10
x=foo
eval y='$'$x
echo $y #输出10
exec
将当前shell替换为一个不同的程序
exec wall "Thanks for all the fish"
修改当前文件描述符:
exec 3<afile # 文件描述符3被打开以从afile文件中读取
exit n命令
使脚本程序以退出码n结束运行。
shell脚本编程中,0表示成功,1~125是脚本程序可以使用的错误代码。其余数字具有保留含义
退出码 | 说明 |
---|---|
126 | 文件不可执行 |
127 | 命令未找到 |
128及以上 | 出现一个信号 |
export
export命令将作为它参数的变量导出到子shell中,并使之在子shell中有效。
set -a
或set -allexport
命令 将导出它之后声明的所有变量
expr
将它的参数当做一个表达式来求值。
# 反引号`字符使x取值为命令expr $x + 1的执行结果
x=`expr $x + 1`
# or
x=$(expr $x + 1)
printf
printf "format string" param1 param2
printf "%s %d\t%s" "Hi there" 15 people
# Hi there 15 people
return
set
为shell设置参数变量
echo the date is $(date)
set $(date)
echo The month is $2
shift
把所有参数变量左移一个位置,使$2变为$1,$3变为$2。$0不变
while [ "$1" != "" ]; do
echo "$1"
shift
done
trap
接收到信号后所采取的行动
trap -l
列出信号
trap commond signal
#!/bin/bash
#!/bin/env python 是更为通用的写法
trap 'rm -f /tmp/my_tmp_file_$$' INT
echo creating file
date > /tmp/my_tmp_file_$$
echo "press interrupt (ctrl-c) to interrupt ..."
while [ -f /tmp/my_tmp_file$$ ]; do
echo File exits
sleep 1
done
echo the file no longer exists
trap INT #
unset
从环境中删除变量或函数,不能删除shell本身定义的只读变量
foo="hello"
echo $foo
unset foo
echo $foo
搜索相关
find / -name test
grep
字符 | 含义 |
---|---|
^ | 指向一行的开头 |
$ | 指向一行的结尾 |
. | 任意单个字符 |
[] | 方括号同通配符 |
匹配模式 | 含义 |
---|---|
[:alnum:] | 字母与数字 |
[:alpha:] | 字母 |
[:ascii:] | ASCII字符 |
[:blank:] | 空格或制表符 |
[:digit:] | 数字 |
简单示例
for i in 1 2
do
my_file_${i}
done
类型 | 选项 | 选项 | 含义 |
---|---|---|---|
dialog --title "Check me" --checklist "Pick Numbers" 15 25 3 1 "one" "off" 2 "two" "on" 3 "three" "off"
#!/bin/bash
#!/bin/env python 是更为通用的写法
dialog --title "Qestionnaire" --msgbox "welcom to my simple survey" 9 18
dialog --title "Confirm" --yesno "are you willing to take part?" 9 18
# 用环境变量$?来检查用户是否选择了yes
if [ $? != 0 ]; then
dialog --infobox "Thank you anyway" 5 20
sleep 2
dialog --clear
exit 0
fi
# 使用一个输入框来询问用户的姓名。重定向标准错误流2到临时文件_1.txt,然后再将其放入变量Q_NAME中
dialog --title "Questionnaire" --inputbox "please enter your name" 9 30 2>_1.txt
Q_NAME=$(cat _1.txt)
# 用户选择的菜单项编号将被保存到临时文件_1.txt中,同时这个结果被放入变量Q_MUSIC中
dialog --menu "$Q_NAME, what music do you like best?" 15 30 4 1 "Classical" 2 "Jazz"\
3 "Country" 4 "Other" 2>_1.txt
Q_MUSIC=$(cat _1.txt)
if [ "$Q_MUSIC" == "1" ]; then
dialog --title "Likes Classical" --msgbox "Good choice!" 12 25
else
dialog --title "Doesn't like Classical" --msgbox "Shame" 12 25
fi
sleep 2
dialog --clear
exit 0
Linux一切皆文件
访问不同类型的设备和访问文件一样
对于文件操作,大多数情况下,需要知道5个基本的函数——open, close, read, write, ioctl
文件,除了本身包含的内容以外还包含一个名字和一些属性信息(即,“管理信息”,包括修改时间,访问权限等)。这些属性被保存在文件的inode中,它是文件系统中的一个特殊的数据块。
目录是用于保存其它文件的节点号和名字的文件。
UNIX和Linux中比较重要的设备文件有3个:/dev/console, /dev/tty, /dev/null
/dev目录中的其他设备包括:硬盘、软盘、通信端口、声卡以及一些代表系统内部工作状态的设备。
操作系统内核,由一组设备驱动程序组成。
ioctl: 把控制信息传递给设备驱动程序。用于提供一些与特定硬件设备有关的必要控制,所以它的用法随设备的不同而不同。
函数库封装的系统调用,提供更高层次的用法。如stdio.h
用户程序、函数库、系统调用、内核、设备驱动以及硬件之间的关系
文件描述符相当与一个索引,它指向操作系统的一个对象
write
的作用是把缓冲区buf的年n个字节写入与文件描述符fildes关联的文件中。它返回实际写入的字节数。如果文件描述符有错误或者底层的设备驱动程序对数据块长度比较敏感,该返回值可能会小于n。如果返回0,则表示未写入任何数据;如果返回-1,表示调用出错,错误码会保存在全局变量errno里。
#include
size_t wirte(int fd, const void* buf, size_t nbytes);
read
的作用是从与文件描述符fd关联的文件里读入n个字节的数据,并把它们放到数据区buf中。它返回实际读入的字节数,这可能会小于请求的字节数。如果返回0,则表示未读入任何数据,已达到了文件尾;如果返回-1,表示调用出错,错误码会保存在全局变量errno里。
open
建立了一条到文件或设备的访问路径。如果调用成功,它将返回一个可以被read、write和其他系统调用使用的文件描述符。这个文件描述符是唯一的,它不会与任何其他运行中的进程共享 。如果两个进程同时打开一个文件,它们会分别得到两个不同的文件描述符。如果它们都对文件进行写操作,那么它们会各写各的,它们分别接着上次离开的位置继续往下写。它们的数据不会交织在一起,而是彼此互相覆盖。两个程序对文件的读写位置(偏移值)不同。可以通过使用文件锁来防止出现冲突。
#include
#incldue <sys/types.h>
#incldue <sys/stat.h>
int open(const char* path, int oflags);
int open(const char* path, int oflags, mode_t mode);
--oflags: 文件访问模式
- O_RDONLY
- O_WRONLY
- O_RDWR
- O_APPEND
- O_TRUNC: 覆盖
- O_CREAT: 如果需要,就按参数mode中给出的访问模式创建文件
- O_EXCL: 与O_CREAT一起使用,确保调用者创建出文件。open调用是一个原子操作,也即是在说,
它只执行一个函数调用。使用这个可选模式可以防止两个程序同时创建同一个文件。如果文件已存在,open调用将失败。
当使用O_CREAT时,必须使用三个参数格式的open调用。第三个参数mode是几个标志位按位或后得到的,这些标志位在头文件sys/stat.h中:
close
终止文件描述符与其对应文件之间的关联,并释放资源。成功0,失败-1。
ioctl
大杂烩。它提供了一个用于控制设备及其描述符行为和配置底层服务的接口。终端、文件描述符、套接字甚至磁带都可有为它们定义的ioctl。
#include
int ioctl(int fd, int cmd, ...);
理论上,下面这两段程序所花费的时长将不同,其主要耗费在了系统调用上,所以对其使用进行优化是有必要的。
int main()
{
int fd_src;
fd_src = open("txt.txt" , O_RDONLY);
if (fd_src < 0) perror("open, txt.txt");
char buf[1024] = {'\0'};
size_t rdsz = 0;
int fd_dst;
fd_dst = open("txt2.txt", O_WRONLY | O_CREAT, 0777);
while ( (rdsz = read(fd_src, buf, 1024)) > 0 )
{
if (write(fd_dst, buf, rdsz) < rdsz)
{
perror("write");
exit(0);
}
}
close(fd_src);
close(fd_dst);
}
int main()
{
int fd_src;
fd_src = open("txt.txt" , O_RDONLY);
if (fd_src < 0) perror("open, txt.txt");
char buf[1] = {'\0'};
size_t rdsz = 0;
int fd_dst;
fd_dst = open("txt2.txt", O_WRONLY | O_CREAT, 0777);
while ( (rdsz = read(fd_src, buf, 1)) > 0 )
{
if (write(fd_dst, buf, rdsz) < rdsz)
{
perror("write");
exit(0);
}
}
close(fd_src);
close(fd_dst);
}
lseek
对文件描述符的读写指针进行设置。
#include
#include
off_t lseek(int fd, off_t offset, int whence);
fstat, stat, lstat
返回与打开的文件描述符相关的文件的状态信息,该信息将会写到一个buf结构中,buf的地址以参数形式传递给fstat
#include
#include
#include
int fstat(int fd, struct stat *buf);
int stat(const char* path, struct stat *buf);
int lstat(const char* path, struct stat *buf);
dup, dup2
复制文件描述符,使得多个fd指向同一个对象
#include
int dup(int fd);
int dup2(int fd, int fd2);
FILE* fopen(const char* filename, const char* mode);
size_t fread(void*ptr, size_t size, size_t nitems, FILE* stream);
size_t fwrite(const void*ptr, size_t size, size_t nitems, FILE* stream);
int fclose(FILE*stream);
int fflush(FILE* stream);
int fseek(FILE* stream, long int offset, int whence);
char* fgets(char*s, int n, FILE*stream);
char* gets(char* s);
printf, fprintf, sprintf
scanf, fscanf, sscanf
fgetpos: 获得文件流的当前位置
fsetpos: 设置文件流的当前位置
ftell: 返回文件流当前位置的偏移值
rewind: 重置文件流里的读写位置
freopen:重新使用一个文件流
setvbuf:设置文件流的缓冲机制
remove:相当于unlink函数,但如果它的path参数是一个目录的话,其作用就相当于rmdir
int ferror(FILE* stream);
int feof(FILE* stream);
void clearerr(FILE* stream);
int fileno(FILE* stream);
FILE* fdopen(int fd, const char*mode);
chmod – syscall
int chmod(const char* path, mode_t mode);
chown – syscall
int chown(const char* path, uid_t owner, gid_t group);
unlink, link, symlink – syscall
unlink系统调用删除一个文件的目录项并减少它的连接数。如果一个文件的连接数减少到0,并且没有进程打开它,这个文件就会被删除。(目录项总是被立刻删除,但文件所占用的空间要等到最后一个进程关闭它之后才会被系统回收)先用open创建一个文件然后对其调用unlink,可以用来创建临时文件,这些文件只有在被打开的时候才能被程序使用,当程序退出并且文件关闭时它们就会被自动删除掉
link将创建一个指向已有文件path1的新链接。新目录项由path2给出。可以通过symlik创建符号链接。
int unlink(const char* path);
int link(const char* path1, const char* path2);
int symlink(const char* path1, const char* path2);
mkdir, rmdir – syscall
#include
#include
int mkdir(const char* path, mode_t mode);
#include
int rmdir(const char* path);
chdir – syscall, getcwd – function
chdir用于切换目录
getcwd用于显示当前工作目录
#include
int chdir(const char* path);
char* getcwd(char* buf, size_t size);
文件流(FILE*)
目录流(DIR*)
opendir – function
#include
#include
DIR* opendir(const char* name);
readdir – function
每次调用该函数都会返回目录项中的下一项,如果发生错误或到达目录尾放回NULL
#include
#include
struct dirent* readdir(DIR* dirp);
telldir – function
telldir函数的返回值记录着一个目录流里的当前位置,可以再随后的seekdir中利用这个值来重置目录扫描到当前位置。
long int telldir(DIR* dirp);
seekdir – function
设置目录流的目录项指针。loc的值用来设置指针位置,它应该通过前一个telldir获得
void seekdir(DIR* dirp, long int loc);
closedir
int closedir(DIR* dirp);
#include
strerror函数把错误代码映射为一个字符串,该字符串对发生的错误类型进行说明
perror函数也把errno变量中报告的当前错误映射到一个字符串,并把它输出到标准错误输出流
char* strerror(int errnum);
void perror(const char* s);
proc是伪文件系统,它提供了与内核数据结构的接口。通常挂载在/proc下。一般由系统自动挂载,也可手动挂载: mount -t proc proc /proc。/proc中的大多数文件是只读的,有些是可写的用以修改内核变量的值。
fcntl对底层文件描述符提供了更多的操作方法
#include
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
mmap函数创建一个指向一段内存区域的指针,该内存区域与可以通过一个打开的文件描述符访问的文件的内容相关联。
#include
// 以下两个函数,线程安全
// success: 有效地址; failure: (void*)-1, errno be set
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
// sucess: 0; failure:-1, errno be set
int munmap(void* addr, size_t length);
return: 映射起始地址
addr: 创建的新映射的起始地址; 如果为NULL,内核会自动选择合适的地址(推荐的方式),如果不为NULL,则内核选择它作为映射的起始地址(不推荐)
len: 映射的长度(必须大于0)
prot: 映射内存的权限。以下可安位取或,不能与文件的打开模式冲突
PROT_EXEC:
PROT_READ:
PROT_WRITE:
PROT_NONE: 不能访问,不能和以上取或
flags: 参数确定映射的更新对于映射同一区域的其他进程是否可见,以及更新是否会传递到底层文件。此行为由以下因素决定
在标志中恰好包含以下值之一:
fd: 文件描述符(文件映射,与匿名映射相反)。调用mmap后fd可直接关闭而不影响mmap
offset: 被映射fd的起始地址偏移量,必须为页大小( sysconf(_SC_PAGE_SIZE)返回值 )的整数倍
NOTE:
文件以页面大小的整数被进行映射,映射尾部多余的补0,并且对多余部分的修改不会写入文件。此时增删文件的大小是未定义行为。
#include
#include
#include
#include
#include
#include
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int
main(int argc, char *argv[])
{
char *addr;
int fd;
struct stat sb;
off_t offset, pa_offset;
size_t length;
ssize_t s;
if (argc < 3 || argc > 4) {
fprintf(stderr, "%s file offset [length]\n", argv[0]);
exit(EXIT_FAILURE);
}
fd = open(argv[1], O_RDONLY);
if (fd == -1)
handle_error("open");
if (fstat(fd, &sb) == -1) /* To obtain file size */
handle_error("fstat");
offset = atoi(argv[2]);
pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
/* offset for mmap() must be page aligned */
if (offset >= sb.st_size) {
fprintf(stderr, "offset is past end of file\n");
exit(EXIT_FAILURE);
}
if (argc == 4) {
length = atoi(argv[3]);
if (offset + length > sb.st_size)
length = sb.st_size - offset;
/* Can't display bytes past end of file */
} else { /* No length arg ==> display to end of file */
length = sb.st_size - offset;
}
addr = (char*)mmap(NULL, length + offset - pa_offset, PROT_READ,
MAP_PRIVATE, fd, pa_offset);
if (addr == MAP_FAILED)
handle_error("mmap");
s = write(STDOUT_FILENO, addr + offset - pa_offset, length);
if (s != length) {
if (s == -1)
handle_error("write");
fprintf(stderr, "partial write");
exit(EXIT_FAILURE);
}
munmap(addr, length + offset - pa_offset);
close(fd);
exit(EXIT_SUCCESS);
}
#include
int msync(void* addr, size_t len, int flags);
把在该内存段的某个部分或整段中的修改写回到被映射的文件中(或者从被映射的文件里读出)
#include
#include
#include
#include
#include
#include
typedef struct
{
int integer;
char string[24];
} RECORD;
#define NRECORDS (100)
int main(int argc, const char* argv[])
{
RECORD record, *mapped;
FILE *fp;
fp = fopen("records.dat", "w+");
for (int i = 0; i < NRECORDS; ++i) {
record.integer = i;
sprintf(record.string, "RECORD-%d", i);
fwrite(&record, sizeof(record), 1, fp);
}
fclose(fp);
fp = fopen("records.dat", "r+");
fseek(fp, 43*sizeof(RECORD), SEEK_SET);
fread(&record, sizeof(RECORD), 1, fp);
record.integer = 143;
sprintf(record.string, "RECORD-%d", record.integer);
fseek(fp, 43*sizeof(RECORD), SEEK_SET);
fwrite(&record, sizeof(RECORD), 1, fp);
fclose(fp);
int fd = open("records.dat", O_RDWR);
mapped = (RECORD*)mmap(NULL, NRECORDS*sizeof(RECORD), PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);
mapped[43].integer = 243;
sprintf(mapped[43].string, "RECORD-%d", mapped[43].integer);
msync(mapped, NRECORDS*sizeof(RECORD), MS_ASYNC);
munmap(mapped, NRECORDS*sizeof(RECORD));
}
main的参数由shell传递
命令行解析函数:
#include
int getopt(int argc, char* const argv[], const char* optstring);
extern char* optarg;
extern int optind, opterr, optopt;
#include
int getopt_long(int argc, char* const argv[], const char*optstring,
const struct option* longopts, int *longindex);
int getopt_long_only(int argc, char* const argv[], const char* optstring,
const struct option* longopts, int* longindex);
eg:
# -n -t val
#include
#include
#include
int
main(int argc, char *argv[])
{
int flags, opt;
int nsecs, tfnd;
nsecs = 0;
tfnd = 0;
flags = 0;
while ((opt = getopt(argc, argv, "nt:")) != -1) {
switch (opt) {
case 'n':
flags = 1;
break;
case 't':
nsecs = atoi(optarg);
tfnd = 1;
break;
default: /* '?' */
fprintf(stderr, "Usage: %s [-t nsecs] [-n] name\n",
argv[0]);
exit(EXIT_FAILURE);
}
}
printf("flags=%d; tfnd=%d; nsecs=%d; optind=%d\n",
flags, tfnd, nsecs, optind);
if (optind >= argc) {
fprintf(stderr, "Expected argument after options\n");
exit(EXIT_FAILURE);
}
printf("name argument = %s\n", argv[optind]);
/* Other code omitted */
exit(EXIT_SUCCESS);
}
int main(int argc, const char* argv[])
{
int opt = -1;
while ( (opt = getopt(argc, argv, "alhf:d:")) != -1 )
{
printf("-----st-------");
printf("optind:%d, opt:%c\n", optind, optopt);
switch (opt)
{
case 'a':
case 'l':
case 'h':
printf("option:%c\n", opt);
break;
case 'f':
printf("-f val:%s\n", optarg);
break;
default:
printf("optind:%d, opt:%c\n", optind, optopt);
break;
}
printf("-----ed-------");
printf("optind:%d, opt:%c\n", optind, optopt);
}
printf("optind:%d, opt:%c\n", optind, optopt);
}
getopt_long
struct option longopts[] = {
{"initialize", 0, NULL, 'i'},
{"file", 1, NULL, 'f'},
{"list", 0, NULL, 'l'},
{"restart", 0, NULL, 'r'},
{0,0,0,0}
};
while( (opt = getopt_long(argc, argv, "if:lr")) != -1 );
#include
char* getenv(const char* name);
int putenv(const char* string);
getenv函数以给定的名字搜索环境中的一个字符串,并返回与该名字相关的值。若变量不存在或变量没有值,则返回null。它返回的字符串存储在静态区
putenv函数以一个格式为“名字=值”的字符串作为参数,并将该字符串加到当前环境变量中。如果失败返回-1。
NOTE:
环境仅对程序本身有效。变量的值不会从子进程传播到父进程
#include
#include
int main(int argc, char *argv[])
{
char* home_val = getenv("HOME");
printf("%s\n", home_val);
putenv("HELLO=YES");
home_val = getenv("HELLO");
printf("%s\n", home_val);
}
程序经常使用环境变量来改变它们的工作方式。
用户可在以下方式设置环境变量的值:在默认环境中设置、通过登录shell读取的.profile文件来设置、使用shell专用的启动文件(rc)或在shell命令行上对变量进行设置
#incldue <unistd.h>
extern char** environ;
GMT | 格林尼治时间 1970年1月1日0点 |
date命令可以获取时间和日期
UTC时间:1970年1月1日零时作为 第0秒
获取时间点:
#include
/* Return the current time and put it in *TIMER if TIMER is not NULL. */
// 返回秒数,跟随系统时间
time_t time (time_t *__timer) __THROW;
/* Get current value of clock CLOCK_ID and store it in TP. */
extern int clock_gettime (clockid_t __clock_id, struct timespec *__tp) __THROW;
__clock_id:CLOCK_REALTIME(系统时间),CLOCK_MONOTONIC(单调递增时钟,可用于计算时间间隔)
时间转换:
struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year - 1900 */
int tm_wday; /* Day of the week (0-6, Sunday = 0) */
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* Daylight saving time */
};
// *_r thread safe
// 不带 _r 的函数返回值存储在静态区
char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);
struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);
// 线程安全
time_t mktime(struct tm *tm);
格式化输出时间
// 将struct tm 格式化为 字符串的形式
size_t strftime(char*s, size_t maxsize, const char*format, struct tm* timeptr);
// 读取日期和时间,
/* Parse buf according to FORMAT and store binary time information in timeptr.
The return value is a pointer to the first unparsed character in S. */
char* strptime(const char* buf, const char* format, struct tm* timeptr);
#include
// 返回一个不与任何已存在文件同名的有效文件名(可能与其他程序调用该函数产生的名字同名)
// 关于线程安全等请参考手册
char* tmpnam(char*s); // 返回值存储在静态存储区
// 直接打开一个唯一的临时文件,当对它的引用全部关闭时,该文件会被自动删除
FILE* tmpfile(void);
#include
// unix中类似函数
char* mktemp(char* template);
int mkstemp(char* template); // 返回文件描述符
template参数必须是一个以6个X字符结尾的字符串,以上函数以给定的模板为基础创建一个唯一的文件名。用有效文件名字符的一个唯一组合来替换这些X字符
init进程PID=1,除了该进程外,所有Linux程序都是由其他程序或用户启动的。
程序能够通过检查环境变量和读取系统时钟来在很大程度上了解它所处的运行环境。程序也能够发现它的使用者的相关信息。
当一个用户要登录Linux时,通过用户名和密码验证,用户就可以进入一个shell。每个用户都有一个唯一的UID,Linux运行的每个程序都是以某个用户的名义在运行,都有一个关联的UID。
UID
为uid_t类型,在sys/types.h中定义,一般情况下,用户的UID值都大于100.
#include
#include
// 返回启动程序的用户的UID
uid_t getuid(void);
// 返回与当前用户关联的登录名
char* getlogin();
系统文件/etc/passwd 包含一个用户账号数据库。如果要获取相关信息建议使用如下相关函数:
sdf: x :1000:1000:sdf,,,:/home/sdf:/bin/bash
用户名 | 加密口令 | UID | GID | 全名 | 家目录 | 默认shell |
---|---|---|---|---|---|---|
sdf |
x |
1000 |
1000 |
sdf,,, |
/home/sdf |
/bin/bash |
获取用户信息的编程接口:
#include
#include
struct passwd* getpwuid(uid_t uid);
struct passwd* getpwnam(const char* name);
// 依次取出文件数据项
struct passwd *getpwent();
// 终止处理过程
void endpwent(void);
// 再次调佣getpwent时将返回首x
void setpwent(void);
#include
/* Get the real user ID of the calling process. */
extern __uid_t getuid (void) __THROW;
/* Get the real group ID of the calling process. */
extern __gid_t getgid (void) __THROW;
/* Return the login name of the user.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern char *getlogin (void);
#include
/* A record in the user database. */
struct passwd
{
char *pw_name; /* Username. */
char *pw_passwd; /* Hashed passphrase, if shadow database
not in use (see shadow.h). */
__uid_t pw_uid; /* User ID. */
__gid_t pw_gid; /* Group ID. */
char *pw_gecos; /* Real name. */
char *pw_dir; /* Home directory. */
char *pw_shell; /* Shell program. */
};
#include
// These system calls are used to access or to change the system hostname.
int gethostname(char* name, size_t namelen);
int sethostname(const char* name, size_t len);
long gethostid(void);
#include
// get name and information about current kernel
int uname(struct utsname *name);
/* Structure describing the system and machine. */
struct utsname
{
/* Name of the implementation of the operating system. */
char sysname[_UTSNAME_SYSNAME_LENGTH];
/* Name of this node on the network. */
char nodename[_UTSNAME_NODENAME_LENGTH];
/* Current release level of this implementation. */
char release[_UTSNAME_RELEASE_LENGTH];
/* Current version level of this release. */
char version[_UTSNAME_VERSION_LENGTH];
/* Name of the hardware type the system is running on. */
char machine[_UTSNAME_MACHINE_LENGTH];
#if _UTSNAME_DOMAIN_LENGTH - 0
/* Name of the domain of this node on the network. */
# ifdef __USE_GNU
char domainname[_UTSNAME_DOMAIN_LENGTH];
# else
char __domainname[_UTSNAME_DOMAIN_LENGTH];
# endif
#endif
};
/var/log/
#include
void syslog(int priority, const char* message, arguments...);
void closelog(void); void openlog(const char* ident, int logopt, int facility);
int setlogmask(int maskpri);
// 实测会被记录到 /var/log/syslog下:
// syslog(LOG_USER, "hi, this is log... - %m\n");
// Oct 28 09:29:34 MyComputer main: hi, this is log... - Success
pid_t getpid(void);
pid-t getppid(void); // 获得父进程pid
#include
int getpriority(int which, id_t who);
int getpriority(int which, id_t who, int priority);
int getrlimit(int resource, struct rlimit *r_limit);
int setrlimit(int resource, const struct rlimit *r_limit);
int getrusage(int who, struct rusage* r_usage);
一个程序耗费的CPU时间 = 用户时间(程序执行自身指令所耗费的时间)+系统时间(操作系统为执行程序所耗费的时间,即输入输出、系统调用时间)。
// 确定当前进程优先级
priority = getpriority(PRIO_PROCESS, getpid());
ulimit -a
# 查看操作系统对软件的资源限制
检查文件描述符是否连接到一个终端
#include
int isatty(int fd);
输出缓冲是由libc实现的,那么输入缓冲应该也是由libc实现的
Linux提供了一个特殊设备/dev/tty
(点击页内跳转) ,该设备始终执行当前终端或当前的登录会话。
#include
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int actions, const struct* termios_p);
struct termios
{
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
};
输入模式控制输入数据(终端驱动程序从串口或键盘接收到的字符)在被传递给程序之前的处理方式。
c_iflag成员宏:
控制输出字符的处理方式,即由程序发送出去的字符在传递到串口或屏幕之前是如何处理的。
c_oflag成员宏:
控制中断的硬件特性
c_cflag成员的宏:
控制终端的各种特性
c_lflag宏:
特殊字符是一些字符组合,如Ctrl+C,
关闭回显,修改终端速度等等
终端类型
echo $TERM
xterm-256color
shell通过xterm-256color程序(一个X视窗系统中的终端仿真程序)或是提供类似功能的程序运行的
查看终端当前信息属性
stty -a
speed 38400 baud; rows 75; columns 274; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = ; eol2 = ; swtch = ; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc
输出terminfo数据项的可读版本
infocmp xterm
#include
// 将当前终端类型设置为参数term指向的值
int setupterm(char* term, int fd, int *errret);
- -1: terminfo数据库不存在
- 0:terminfo数据库中没有匹配的数据项
- 1:成功
该函数成功返回常量OK,失败返回ERR。如果errret被设置为空指针,setupterm函数会在失败时输出一条诊断信息并导致程序直接退出
just like this:
#include
#include
#include
#include
int main()
{
setupterm("unlisted, fileno(stdout), (int*)0);
printf("Done.\n");
exit(0);
}
https://gitee.com/overcast-to-clear/linux-programming-/tree/master/%E4%BA%94%E3%80%81%E7%BB%88%E7%AB%AF%E6%8E%A7%E5%88%B6%E7%A8%8B%E5%BA%8F%E7%A4%BA%E4%BE%8B
https://gitee.com/overcast-to-clear/linux-programming-/blob/master/%E4%BA%94%E3%80%81%E7%BB%88%E7%AB%AF%E6%8E%A7%E5%88%B6%E7%A8%8B%E5%BA%8F%E7%A4%BA%E4%BE%8B/example.c
MS-DOS中有kbhit函数,用于在没有实际进行读操作之前检测是否某个键被按过,而Linux中没有次函数。
特殊文件“/dev/tty”总是指向当前终端
Linux提供了虚拟控制台的功能,一组终端设备共享PC电脑的屏幕、键盘和鼠标。通常,一个Linux安装将配置多个虚拟控制台。虚拟控制台通过字符设备文件/dev/ttyN使用
使用命令who 和ps,即可看到目前登录进系统的用户,以及在这个虚拟控制台上运行的shell和执行的程序。
sdf@MyComputer:/dev$ who
sdf tty2 2023-10-28 08:20 (tty2)
sdf tty3 2023-10-28 21:31
sdf@MyComputer:/dev$ ps -e | grep tty
2355 tty2 00:00:00 gdm-wayland-ses
2361 tty2 00:00:00 gnome-session-b
22039 tty3 00:00:00 login
22122 tty3 00:00:00 bash
可以通过Ctrl+Alt_F
在不同的ttyN
之间切换,字符界面可以通过Alt+F
在不同的虚拟控制台之间进行切换。
可以同时在Linux系统上运行多个X视窗会话:
startx -- :1
/usr/lib/xorg/Xorg.wrap: Only console users are allowed to run the X server
在其他方面,虚拟控制台的行为都与普通硬件终端一样。
PTY
伪终端的行为与虚拟控制台的行为非常相似,唯一的区别是伪终端没有对应的硬件设备。它们可以用来为其他程序提供终端形式的接口。
chatgpt 3.5
伪终端(Pseudo Terminal,PTY):
虚拟终端(Virtual Terminal):
区别:
Bing chat
在Linux系统中,伪终端(也被称为pty)是一对字符设备,包括伪终端主设备(master)和伪终端从设备(slave)。其中,slave对应/dev/pts/目录下的一个文件,而master则在内存中标识为一个文件描述符12。
伪终端的主要作用是模拟物理终端的行为。它由运行在用户态的终端模拟器提供。Master端更接近用户显示器、键盘的一端,而slave端则运行在虚拟终端上的命令行接口(CLI)程序。Linux的伪终端驱动程序会把master端(如键盘)写入的数据转发给slave端供程序输入,同时把程序写入slave端的数据转发给master端供显示器驱动等读取12。
虚拟终端是Linux系统中的一个概念,它是通过软件在物理机上虚拟出来的终端3。Linux默认存在6个虚拟终端,可以使用快捷键(Ctrl + Alt + F1~F6)切换3。这些虚拟终端使用tty(teletypewriter)显示3。虚拟终端并不直接对应硬件设备,但它们模拟了物理设备(如键盘和显示器)的行为13。
总的来说,伪终端和虚拟终端都是Linux系统中用于提供用户交互界面的重要工具。它们使得用户可以在同一时间运行多个会话,并且可以方便地在这些会话之间切换
curses库简介
上一章介绍了如何加强对字符输入的控制,以及如何以终端无关的方式提供字符输出。使用通用终端接口(GTI或termios)和通过tparm以及相关函数控制escape转义系列都会导致大量的底层代码。
而curses库函数提供了高层接口,它位于简单的文本行程序和完全图形化界面的X视窗系统程序(如GTK+/GNOME和qt/KED)之间。
stdscr与终端屏幕的尺寸完全一样。窗口可以互相重叠,可以拥有自己的多个子窗口,但每个子窗口必须总是被包含在它的父窗口内。
stdscr数据结构对应的是“标准屏幕”,与stdout非常相似,是个缓存;curscr数据结构和stdscr相似,但它对应的是当前屏幕的样子。在程序调用refresh之前,输出到stdscr上的内容不会显示在屏幕上。curses函数库会在refresh函数被调用时比较stdscr与curscr(屏幕当前的样子)之间的不同之处,然后用这两个数据结构之间的差异来刷新屏幕。
移动和更新窗口
int touchwin(WINDOW *window_ptr);
int scrollok(WINDOW *window_ptr, bool scroll_flag);
int scroll(WINDOW *window_ptr);
touchwin非常特殊,他的作用是通知curses函数库其指针指向的窗口内容已经发生改变,在下次调用wrefresh函数是,curses必须重新绘制该窗口,即使用户实际并未修改其内容。当屏幕上重叠着多个窗口时,可以用过该函数来安排要显示的窗口。
优化屏幕刷新
目的:尽量减少在屏幕上绘制的字符数目
#include
int wnoutrefresh(WINDOW* window_ptr);
int doupdate(void);
#include
WINDOW* subwin(WINDOW*parent, int num_of_lines, int num_of_cols, int start_y, int start_x);
int delwin(WINDOW* win_to_del);
#include
WINDOW *newpad(int num_of_lines, int num_of_cols);
// 将pad从坐标(pad_row, pad_col)开始的区域写到屏幕上指定的显示区域,该显示区域
// 从(scr_row_min,scr_col_min)到(scr_row_max,scr_col_max)
int prefresh(
WINDOW *pad_ptr,
int pad_row,
int pad_col,
int scr_row_min,
int scr_col_min,
int scr_row_max,
int scr_col_max
);
pnoutrefresh;类似wnoutrefresh
当物理内存耗尽时,内核便会使用交换空间(swap space),在Linux中,交换空间是一个在安装系统是分配的独立的磁盘区域。
open系统调用并且带上O_CREAT和O_EXCL标志,这样能够以一个原子操作同时完成两项工作:确定文件不存在,然后创建它。
#incldue <unistd.h>
#include
#include
#include
#include
const char* lock_file = "/tmp/LCK.test2";
int main(){
int fd;
int tries = 10;
while(tries--){
fd = open(lock_file, O_RDWR|O_CREAT|O_EXCL, 0444);
if (fd == -1) {
printf("%d -lock already present\n", getpid());
sleep(3);
} else {
printf("%d - i have exclusive access\n", getpid());
sleep(1);
close(fd);
unlink(lock_file);
}
}
return 0;
}
区域锁定:
#include
int fcntl(int fd, int command, ...);
int fcntl(int fd, int command, struct flock *flock_st);
flock(文件锁)结构依赖具体的实现,但它至少包含下属成员:
文件中的每个字节在任意时刻只能拥有易总类型的锁:共享锁、独占锁、解锁
command参数:
在使用文件锁时应该使用系统调用read和write读写,而不是用libc读写。
文件锁测试 P246
socket是syscall
套接字是一种通讯机制,凭借这种机制,客户/服务器系统的开发工作即可以在本地单机上进行,也可以跨网络进行。Linux所提供的功能(如打印、连接数据库和提供web页面)和网络工具(如用于远程登录的rlogin和用于文件传输的ftp)通常都是通过套接字来进行通信的。
https://gitee.com/overcast-to-clear/linux-programming-.git
用完一个套接字后,就应该把它删除,即使在程序因接收到一个信号而异常终止的情况下也应该这么做。
client:
1. 创建socket
2. 根据socket以及addr建立connect
3. 读写socket文件描述符
4. close
server
1. 创建socket
2. 指定地址
3. 将地址与sfd 绑定(bind)
4. 监听该sfd
5. 调用accept等待客户端链接
6. 读写客户端对应的文件描述符
7. close
https://blog.csdn.net/surfaceyan/article/details/125341896
套接字特性由3个属性确定:域(domain,又称协议族,protocal family),类型(type)和协议(protocal)。
extern int socket (int domain, int type, int protocol);
参数:
- domain: AF_INET, ipv4
AF_INET6, ipv6
AF_UNIX, unix文件系统域
- type: - 流套接字,SOCK_STREAM, TCP/IP
- 数据报套接字, SOCK_DGRAM, UPD/IP
- protocal: 填0,默认即可
X/Open规范中在同文件netdb.h中定义了一个常量IPPORT_RESERVED
代表保留端口号的最大值。
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
/* Structure describing the address of an AF_LOCAL (aka AF_UNIX) socket. */
struct sockaddr_un
{
sa_family_t sun_family; // AF_UNIX
char sun_path[108]; /* Path name. */
};
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr)
- __SOCKADDR_COMMON_SIZE
- sizeof (in_port_t)
- sizeof (struct in_addr)];
};
/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
/* Structure describing a generic socket address. */
struct sockaddr
{
sa_family_t sa_family; /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
要想让通过socket调用创建的套接字可以被其他进程使用,服务器程序就必须给该套接字命名。这样,AF_UNIX套接字就会关联到一个文件系统的路径名。AF_INET套接字就会关联到一个IP端口号。
#include
int bind(int socket, const struct sockaddr* address, size_t address_len);
为了能够在套接字上接收进入的链接,服务器程序必须创建一个队列来保存未处理的请求。它用listen系统调用来完成该工作。
#include
int listen(int socket, int backlog);
backlog代表允许的队列中的最大数量,超过这个数量后,再往后的连接将被拒接,导致客户端连接请求失败。
通过accept系统调用来等待客户端建立对该套接字的连接
#include
int accept(int socket, struct sockaddr*address, size_t*address_len);
accept系统调用只有当客户程序试图连接到有socket参数指定的套接字上时才返回。这里的客户指,在套接字队列中排在第一个的未处理连接。accept函数将创建一个新套接字来与该客户进行通讯,并且返回新套接字的描述符。新套接字的类型和服务器监听套接字类型是一样的。
int flags = fcntl(socket, F_GETFL, 0);
fcntl(socket, F_SETFL, O_NONBLOCK|flags);
客户端程序通过在一个未命名套接字和服务器监听套接字之间建立连接的方法来连接到服务器。
int connect(int socket, const struct sockaddr*addr, size_t addr_len);
小于1024的端口号默认是为系统保留的
关于/etc/services
文件:
1)作用
/etc/services文件是记录网络服务名和它们对应使用的端口号及协议。
2)格式
文件中的每一行对应一种服务,它由4个字段组成,中间用TAB或空格分隔,分别表示“服务名称”、“使用端口”、“协议名称”以及“别名”。
服务名"tab" 端口号/协议名 “tab” 别名
kermit 1649/udp
1701/tcp l2f
1701/udp l2f
h323gatedisc 1718/tcp
http 80/tcp www # WorldWideWeb HTTP
https 443/tcp # http protocol over TLS/SSL
https 443/udp # HTTP/3
回路(loopbackq)网络只包含一台计算机,传统上它被称为localhost,标准IP: 127.0.0.1即为本地主机。可在网络主机文件/etc/hosts
找到
用netstat命令可查看网络连接状况 netstat -A inet
其配置文件。。。
setsockopt: https://blog.csdn.net/surfaceyan/article/details/125341896
https://blog.csdn.net/surfaceyan/article/details/125341896
select example
通过selelct调用避免使用子进程
multicli_server.c
udp.c