Linux中umask深入理解

umask(user file-creatiopn mode mask)为用户文件创建掩码,是创建文件或文件夹时默认权限的基础。通常我们可以使用chmod修改linux中文件的权限.umask的作用与chmod的效果相反,具体看下面。

若没有文件掩码时,文件的默认权限为0666,文件夹的默认权限为0777。

原因:

  • 创建文件一般是用来读写,所以默认情况下所有用户都具有读写权限,但是没有可执行权限,所以文件创建的默认权限为0666
  • 而文件夹的x权限表示的是打开权限,所以这个权限必须要有,所以文件夹的默认权限为0777。

用户掩码作用

上述的权限是在没有umask情况下的默认权限。但是系统为了保护用户创建文件和文件夹的权限,此时系统会有一个默认的用户掩码(umask),大多数的Linux系统的默认掩码为022。用户掩码的作用是用户在创建文件时从文件的默认权限中去除掩码中的权限。所以文件创建之后的权限实际为:

1
2
#文件创建权限
默认权限(文件0666,文件夹0777)-umask

所以在用户不修改umask的情况下,创建文件的权限为:0666-0022=0644。创建文件夹的权限为:0777-0022=0755

查看与修改默认掩码

查看用户掩码:

1
2
3
4
#以数字方式查看掩码
umask
# 以符号形式查看掩码
umask -S

可以使用umask命令直接修改掩码。

1
umask 0000

上述方法修改的掩码只在当前tty中生效.若要全局生效,可以讲umask值写在/etc/profile或者.bashrc中

Comment and share

Unix高级编程之signal

signal函数相关的细节描述详见另外两篇篇博客,这里不详细赘述:
https://langzi989.github.io/2017/09/08/C++%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E5%AD%A6%E4%B9%A0/
https://langzi989.github.io/2017/05/04/Wait%E5%87%BD%E6%95%B0%E8%AF%A6%E8%A7%A3/

显示信号的描述

信号的个数可以用宏NSIG获取。

显示信号的描述有三种方法:

1
2
3
4
5
6
7
8
9
#include <string.h>
//first method
char* strsignal(int sig);
//second method,
void psignal(int sig, char* msg);
//third memthod
sys_siglist[sig];

上述三种方法的区别

sys_siglist是直接存储信号描述的数组,一般情况下,推荐使用strsignal。

strsignal和psignal函数对locale敏感,会打印出当地的语言。
调用psignal会在本地的错误出输出流输出,msg:strsignalmsg;

如:

1
2
//此时错误数据流将会打印出:SIGINT:Interrupt
psignal(SIGINT, "SIGINT");

信号集

许多相关的系统调用涉及到一组不同的信号,这时候需要信号集。linux中使用sigset_t结构体来表示信号集。一般情况,信号集是使用掩码实现的,但是可能有一些是其他实现方式。
信号集结构体相关的函数.

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
#include <signal.h>
//初始化空信号集。注意不可使用memset或者静态变量全局变量初始化信号集为空,这样会出问题。因为不是所有的信号集都是通过掩码实现的
//0出错,1成功
int sigemptyset(sigset_t* set);
//初始化信号集包括所有的信号
//0出错,1成功
int sigfillset(sigset_t* set);
//向信号集中添加信号
//0出错,1成功
int sigaddset(sigset_t* set, int sig);
//从信号集中去除信号
//0出错,1成功
int sigdelset(sigset_t* set, int sig);
//检查某一信号是不是在当前信号集中。返回1在,0不在
int sigismember(const sigset_t* set, int sig);
//以下三个为GNU C中的非标准函数,
#define _GNU_SOURCE
//对两个信号集作交集存储于dest中
int sigandset(sigset_t* dest, sigset_t* left, sigset_t* right);
//对两个信号集做并集存储于dest中
int sigorset(sigset_t* dest, sigset_t* left, sigset_t* right);
//判断信号集是否为空
int sigisemptyset(const sigset_t* set);

信号掩码(进程中阻塞信号传递)

内核会为每个进程维护一个信号掩码(标识一组信号),当一个信号被传递到该进程的时候,若该信号在信号掩码中,进程会阻塞该信号的传递,直到将该信号从信号掩码中剔除。

向信号掩码中添加一个信号的方式有以下几种:

  • 当调用信号处理器程序的时候,可将引发该调用的信号自动添加到信号掩码中,这取决于sigaction函数在安装信号时使用的标志。
  • 使用sigaction函数建立信号处理程序时,可以指定一组额外信号,当调用该处理器程序时将阻塞。
  • 使用sigprocmask函数修改进程的信号掩码。

sigprocmask函数

1
2
#include <signal.h>
int sigprocmask(int how, const sigset_t* set, sigset_t* old);

参数:

  • how : 指定修改信号掩码的方式,有三种方式
    • SIG_BLOCK : 向指定信号中添加指定信号.
    • SIG_UNBLOCK: 将指定信号从原有的信号掩码中移除。若被移除的信号掩码不存在不报错
    • SIG_SETMASK: 直接设置(赋值),覆盖原有的值
  • set : 需要设置的新的信号掩码集
  • old: 旧的信号掩码集。可在设置信号掩码集之后回复原有的信号掩码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <unistd.h>
#include <signal.h>
int main() {
time_t start = time(NULL);
sigset_t new_set, pre;
sigemptyset(&new_set);
sigaddset(&new_set, SIGINT);
if (sigprocmask(SIG_SETMASK, &new_set, &pre) == -1) {
std::cout<< "sigprocmask set error" << std::endl;
}
while (true) {
sleep(1);
time_t end = time(NULL);
if (end - start >= 15) {
std::cout << "hahah 接触阻塞" << std::endl;
sigprocmask(SIG_SETMASK, &pre, NULL);
}
}
}

sigpending获取正在等待状态的信号

若进程接收信号被阻塞之后,我们希望获取被阻塞的信号,则可以使用sigpending函数

1
2
#include <signal.h>
int sigpending(sigset_t* set);

使用此函数的场景是:若某个进程接收到被阻塞的信号,如果希望这些信号被移出阻塞队列,此时可以通过sigpending获取被阻塞的信号,然后将此信号的处理器函数IGNORE,并将其剔除信号掩码即可。

在信号被阻塞的时候,不对信号做排队处理,即即使进程阻塞了100个SIGINT信号,此时当SIGINT从信号掩码中去除时,该进程接收的还是只是一个SIGINT信号。

sigaction函数

除了signal函数之外,sigaction系统调用是设置信号处理的另一个选择。sigaction和signal函数相比更加灵活和具有可移植性。sigaction允许在不改变信号处理器程序的情况下获取信号的默认处理方式。

Comment and share

linux中ulimit命令简单使用

系统性能是一个受关注的话题,如何通过最简单的设置来实现有效的性能调优,如何在有限资源的条件下保证程序的运作,ulimit是我们在处理问题的时候经常使用的一种手段。ulimit是一种linux系统内置的功能。下面将列举ulimit的一些常用关键字和简单用法。

ulimit命令参数

ulimit参数命令

注意ulimit设置分软硬区别,加-H为硬,-S为软.默认查询是显示的是软,但使用ulimit进行设置的时候为软硬都作用。

参数 功能 示例
-H 设置硬资源限制,一旦设置不能增加。 ulimit – Hs 64;限制硬资源,线程栈大小为 64K。
-S 设置软资源限制,设置后可以增加,但是不能超过硬资源设置。 ulimit – Sn 32;限制软资源,32 个文件描述符。
-a 显示当前所有的 limit 信息。 ulimit – a;显示当前所有的 limit 信息。
-c 最大的 core 文件的大小, 以 blocks 为单位。 ulimit – c unlimited; 对生成的 core 文件的大小不进行限制。
-d 进程最大的数据段的大小,以 Kbytes 为单位。 ulimit -d unlimited;对进程的数据段大小不进行限制。
-f 进程可以创建文件的最大值,以 blocks 为单位。 ulimit – f 2048;限制进程可以创建的最大文件大小为 2048 blocks。
-l 最大可加锁内存大小,以 Kbytes 为单位。 ulimit – l 32;限制最大可加锁内存大小为 32 Kbytes。
-m 最大内存大小,以 Kbytes 为单位。 ulimit – m unlimited;对最大内存不进行限制。
-n 可以打开最大文件描述符的数量。 ulimit – n 128;限制最大可以使用 128 个文件描述符。
-p 管道缓冲区的大小,以 Kbytes 为单位。 ulimit – p 512;限制管道缓冲区的大小为 512 Kbytes。
-s 线程栈大小,以 Kbytes 为单位。 ulimit – s 512;限制线程栈的大小为 512 Kbytes。
-t 最大的 CPU 占用时间,以秒为单位。 ulimit – t unlimited;对最大的 CPU 占用时间不进行限制。
-u 用户最大可用的进程数。 ulimit – u 64;限制用户最多可以使用 64 个进程。
-v 进程最大可用的虚拟内存,以 Kbytes 为单位。 ulimit – v 200000;限制最大可用的虚拟内存为 200000 Kbytes。

ulimit参数作用范围

  • 针对单个tty生效:在tty中直接运行ulimit命令的作用范围是只对当前tty生效
  • 针对单个用户生效:将命令添加到.bashrc中,将对当前用户生效
  • 针对所有用户生效:将命令添加到/etc/security/limits.conf中,可以设置针对特定用户或者所有用户的限制。

Comment and share

C++函数指针学习

使用函数指针的优点

使用函数指针有助于我们设计出更优秀,更简洁更高效的程序。在下面的情景中我们常用到函数指针:

  • 使用函数指针作为参数
  • 使用函数指针作为返回值
  • 使用函数指针作为回调函数
  • 使用函数指针数组
  • 类的静态方法和非静态方法的函数指针
  • 使用函数指针实现动态绑定
  • 在结构体中定义函数

使用函数指针提高函数的效率

当通过switch case多用多个相同类型的函数的时候,这个时候使用函数指针可以大大简化函数代码并可以明显的提高程序的执行效率。例:

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
#include <iostream>
#include <time.h>
#include <string>
#include <sys/time.h>
using namespace std;
#define StartTime(id) struct timeval __now1105##id;\
gettimeofday(&__now1105##id, 0);
#define EndTime(id) struct timeval __now21105##id; \
gettimeofday(&__now21105##id, 0); \
printf("timer_%s spend time:%d us\n",#id,(__now21105##id.tv_sec-__now1105##id.tv_sec)* 1000000 + (__now21105##id.tv_usec-__now1105##id.tv_usec));
double add(double a, double b) {
return a + b;
}
double sub(double a, double b) {
return a - b;
}
double multi(double a, double b) {
return a * b;
}
double div(double a, double b) {
return a/b;
}
typedef double (*op)(double, double);
void func1(double a, double b, int flag) {
switch(flag) {
case 0:
add(a, b);
break;
case 1:
sub(a, b);
break;
case 2:
multi(a, b);
break;
case 3:
div(a, b);
break;
default:
break;
}
}
//使用函数指针调用函数
void func2(double a, double b, op cb) {
cb(a, b);
}
int main() {
StartTime(func1);
for (int i = 0; i < 100000; i++) {
func1(0.2, 0.034, 3);
}
EndTime(func1);
StartTime(func2);
for (int i = 0; i < 100000; i++) {
func2(0.2, 0.034, multi);
}
EndTime(func2);
}

由上面可以看出,由于上述switch,case中调用的函数类型(返回值类型,参数个数以及对应的类型)完全一致,我们将函数指针以参数的形式传到处理函数中。
运行上述函数的结果如下:

1
2
timer_func1 spend time:2861 us
timer_func2 spend time:2178 us

可以看出使用函数指针的效率远远高于switch case

函数指针用做回调函数

来源于wiki
在计算机程序设计中,回调函数是指通过函数参数传递到其他代码的,某一块可执行代码的引用。这一设计允许底层代码调用在高层定义的子程序。如Linux C中的signal函数就是这样一个例子。

signal底层的其中一个实现版本如下:由其实现可以看出信号处理的回调函数的主要功能是将处理信号的函数指针替换为用户高层自定义的函数地址fun,从而达到当接收到该信号时底层代码调用高层定义代码的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Sigfunc *
signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
} else {
act.sa_flags |= SA_RESTART;
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}

当我们使用signal函数的时候,如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <signal.h>
#include <stdio.h>
void signalcb(int signum) {
printf("this is the signal:%d", signum);
}
int main() {
signal(SIGINT, signalcb);
pause();
return 0;
}

函数指针的本质及声明方法以及赋值

函数指针的本质

函数指针类似于数据指针,其本质实质上是一类函数(返回值类型相同,参数个数以及对应的参数类型相同)的入口地址,即该可执行代码在内存中的起始地址。函数指针可以让我们通过函数地址去调用该函数。有利于实现函数的动态绑定,即在运行时才决定去调用哪个函数。

函数指针的声明方法

函数指针的声明方法有两种,包括:

  • 普通函数的函数指针和类静态成员函数的函数指针
  • 类非静态成员函数的函数指针

普通函数的函数指针和类静态成员函数的函数指针:
int (funptr)(int qa, int qb);
类非静态成员函数的函数指针:
int MyClass::(
funptr)(int qa, int qb);

注意上述函数指针声明时(*funptr)中的括号不能省略,若省略,有可能会产生歧义,其意义就变成了返回值为int*的函数定义了

上述两种函数指针声明不兼容的原因如下:(《深度探索C++对象模型》)
获取类的静态和非静态函数的函数指针的地址都是其在内存中实际的地址。那为什么非静态成员的指针需要绑定(指明类)?原因是类的非静态成员需要操作类的数据成员,所以类的非静态成员需要绑定this指针找到类的数据成员。故对nonstatic函数取地址是不完整的。

函数指针的赋值与使用

函数指针赋值

对于普通指针和类的静态成员指针,有两种赋值方式:

1
2
funptr = fnc1
funptr = &fun1

对于类的非静态成员指针,只能用上面第二种形式赋值。为了保证形式的一致性和避免二义性,一般统一使用取地址符号进行赋值可避免错误出现。

1
funptr = &classInstance.func();

函数指针使用

函数指针的使用类似于赋值。
对于普通指针和类的静态成员函数指针的调用,有两种调用方式:

1
2
3
funptr(1,2);
(*funptr)(1,2);

对于类的静态成员函数,只能用上述第二种方式进行调用,为了保持一致性和避免二义性,一般统一使用使用*来解引用函数指针进行调用.

函数指针作为参数

函数指针是一个类型,将函数指针作为参数传入函数中与其他参数类似。使用方式与上述函数指针用做回调函数中signal接收参数的方式相同。这里不再详解

函数指针作为返回值

既然函数指针是函数的入口地址,所以函数指针也可以作为函数的返回值返回。不过函数指针作为函数的返回值的写反比较复杂。
若一个函数func只有一个参数int, 其返回值类型是float (*) (float, float);则其函数原型如下:

1
float (*func(int op)) (float, float);

函数指针作为函数返回值的原则是,将函数名以及参数写到*后,函数指针的返回值放在最前面,函数指针的参数放在最后面。

由上面的知识我们可以分析一下signal函数原型。

1
void (*signal)(int signo,void (*func)(int)))(int);

其定义了一个有两个参数分别为int和void (func)(int),返回值为void (func)(int)的函数。

其本质上是将函数处理指针替换为用户自定义的函数指针。那为什么需要返回值为void (func)(int)呢?原因是*signal函数的返回值是旧的信号处理函数的指针,我们可以通过这个指针暂时改变signal函数处理信号的方式。之后可以通过返回的指针恢复该信号默认的处理方式。

注意:signal的信号处理其函数中的int参数的含义是:当信号到达的时候,内核将该信号以整形的方式传给处理器函数,即为void (*func)(int sig)中的sig.

使用typedef定义函数指针

一般情况下,如果通过普通的方式定义函数指针,在使用的很不方便。这个时候我们可以通过typedef定义函数指针的新类型。通过typedef定义新类型时与普通类型定义新类型方式不同。对于普通类型,定义方式如下:

1
typedef int64_t int64;

但是对于函数指针,定义方式如下:

1
2
3
4
5
6
7
//此处定义个名字为func的类型,它表示函数指针float (*func)(float, float)类型
typedef float (*func)(float, float);
float function(float a, floatb);
//使用func定义变量
func test = &function;

Comment and share

Linux正则表达式以及Sed使用学习

正则表达式是熟悉和使用Linux系统的最重要的基础,其中grep,find,awk以及sed等对其依赖更大。本节将总结一下Linux中常用的正则表达式使用方法。

正则表达式中特殊字符

  • . : 表示任何单个字符
  • [] : 包含一个字符序列,表示匹配序列中其中一个字符
  • - : 出现在[]中,表示一个序列范围.如[a-z]表示26个小写的英文字母
  • ^ : 出现在[]中,表示对序列去反。如[^a-z]表示匹配不是a-z的其他字符
  • * : 匹配某一个字符的0个或1个或多个实例
  • ? : 匹配某一个字符的0个或1个实例
  • + : 匹配某一个字符的1个或多个实例
  • $ : 匹配行尾。如test$指匹配以test结尾的行
  • ^ : 匹配行首。如^test指匹配以test开始的行
  • \< : 匹配词首
  • > : 匹配词尾
  • \ : 转移特殊字符,如果需要匹配上述特殊字符,用反斜杠转义

sed使用说明

sed命令:

1
sed [OPTION]... {script-only-if-no-other-script} [input-file]...

sed使用

data文件内容

1
2
3
4
this is a dog a
this is a cat
this is a money
this is a fish

用s命令进行替换

替换时若出现单引号,直接将脚本用双引号括起来即可。
1.将一行中第一个匹配的特定字符串替换

1
sed "s/a/an/" data

注意不会改变原文件,只是会打印到标准输出流。若要保存可以重定向到新的文件中。

2.讲一行中所有匹配的特定字符串匹配,使用g参数

1
sed "s/a/an/g" data

3.使用-i参数直接修改文件内容

1
sed -i "s/a/an/g" data

4.在每一行的开头添加内容

1
sed "s/^/#/" data

5.在每一行的结尾添加内容

1
sed "s/$/#/" data

6.指定特定行替换,或指定特定范围内的行替换

1
2
sed "3s/an/a/" data
sed "1,2s/an/a/" data

7.只替换每一行的第一个a:

1
sed "s/a/an/1" data

8.只替换每一行的第二个a:

1
sed "s/a/an/2" data

9.只替换每一行第二个以后的所有a

1
sed "s/a/an/2g" data

多个匹配

多个匹配可以用分好将匹配规则连接。

1
sed "s/an/a/g;s/dog/cat/g" data

上述命令等价于:

1
sed -e "s/an/a/g" -e "s/dog/cat/g" data

显示被匹配的变量

&可以表示被匹配的变量,即若被匹配的变量为”abc”,则&代表abc。

1
2
#功能为在每个an外加[]
sed -e "s/an/[&]/g" data

圆括号匹配

被圆括号括起来的匹配可以当做变量使用,注意圆括号匹配时括号需要”\”转义。变量按顺序使用\1,\2指代。

1
sed "s/this \(.*\) an \(.*\)/\1:\2/" data

结果:

1
2
3
4
is:dog an
is:cant
is:money
is:fish

sed命令

data内容:

1
2
3
4
this is a dog a
this is a cat
this is a money
this is a fish

N命令

N命令的作用是把下一行的命令纳入当做缓冲区。也就是缓冲区包括两行.

1
2
#只匹配缓冲区的第一个this
sed "N;s/this/that/" data

执行命令结果为:

1
2
3
4
that is a dog a
this is a cat
that is a money
this is a fish

1
sed "N;s/\n/,/" data

执行命令结果为:

1
2
this is a dog a,this is a cat
this is a money,this is a fish

a命令和i命令

  1. i命令的作用是在指定行前面插入一行,行号写在i前面,中间空格可有可无;如果不写行号默认在所有行的前面插入

    1
    2
    #在第一行前面插入 this is my test i
    sed "1i this is my test i" data
  2. a命令的作用是在指定行后追加内容,行号写在a前面,若不写表示所有行后。也可用$代表最后一行,不过一定要有空格.

1
2
#在第一行后插入 this is my test a
sed "1a this is my test a" data
  1. 可以使用匹配来添加
1
2
#若匹配到cat,则在当前行后追加一行 this is test match a
sed "/cat/a this is test match a" data

c命令

c命令是替换匹配的行

1
2
#替换第二行
sed "2 c this is test c" data

结果:

1
2
3
4
this is a dog a
this is test c
this is a money
this is a fish

1
2
#用匹配替换
sed "/fish/c this is the test c" data

结果:

1
2
3
4
this is a dog a
this is a cat
this is a money
this is the test c

d命令

d命令删除指定的行,可指定范围

1
2
#删除第2到3行
sed "2,3 d" data

结果:

1
2
this is a dog a
this is a fish

1
2
#删除找到cat指定的行
sed "/cat/d" data

结果:

1
2
3
this is a dog a
this is a money
this is a fish

1
2
#删除第二行到结尾
sed "2,$ d" data

结果:

1
this is a dog a

p命令

p命令的作用是打印匹配到的行.注意打印的时候使用-n参数。sed默认输出处理后的文本,使用-n参数阻止默认输出。

1
2
#打印匹配到cat的行
sed -n "/cat/p" data
1
2
#打印符合从一个模式当另一个模式的行
sed -n "/dog/,/money/p" data

结果:

1
2
3
this is a dog a
this is a cat
this is a money

1
2
#打印从第一行到匹配到的哪一行
sed -n "1,/cat/p" data

结果:

1
2
this is a dog a
this is a cat

=命令

=参数用于打印匹配到的行号

1
2
#打印含有cat的行号
sed -n "/cat/=" data

综合运用

将下列中所有的100替换为当前行号

1
2
3
4
5
6
7
8
9
10
11
12
13
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100
hmset '99_p_100' 'm_19' 100 't_19' 100 'semm_19' 100

shell命令如下:

1
sed = data | sed "N;s/\([0-9]*\)\n\(hmset '99_p_\)[0-9]*\(' 'm_19' \)[0-9]*\( 't_19' \)[0-9]*\( 'semm_19' \)[0-9]*/\2\1\3\1\4\1\5\1/"

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
hmset '99_p_1' 'm_19' 1 't_19' 1 'semm_19' 1
hmset '99_p_2' 'm_19' 2 't_19' 2 'semm_19' 2
hmset '99_p_3' 'm_19' 3 't_19' 3 'semm_19' 3
hmset '99_p_4' 'm_19' 4 't_19' 4 'semm_19' 4
hmset '99_p_5' 'm_19' 5 't_19' 5 'semm_19' 5
hmset '99_p_6' 'm_19' 6 't_19' 6 'semm_19' 6
hmset '99_p_7' 'm_19' 7 't_19' 7 'semm_19' 7
hmset '99_p_8' 'm_19' 8 't_19' 8 'semm_19' 8
hmset '99_p_9' 'm_19' 9 't_19' 9 'semm_19' 9
hmset '99_p_10' 'm_19' 10 't_19' 10 'semm_19' 10
hmset '99_p_11' 'm_19' 11 't_19' 11 'semm_19' 11
hmset '99_p_12' 'm_19' 12 't_19' 12 'semm_19' 12
hmset '99_p_13' 'm_19' 13 't_19' 13 'semm_19' 13

Comment and share

Linux常用命令混淆点记录

ls命令单位问题

ls命令的默认单位为字节
若使用ls时需要指定单位显示的时候,需要添加参数-h或–block-size进行操作。具体操作方法如下:

1
2
3
4
5
6
7
8
9
10
11
#-h表示human,即人可读的方式
#它会按照文件的大小显示单位,
#若文件小于1k,则不显示单位,表示字节
#若文件大于1k但是小于1M,显示的单位为k
#文件大于1M小于1G会显示单位为M,
#以此类推
ls -h ~/test
#按照指定的单位显示
#注意使用此参数需要注意一个问题。若文件小于1G
ls --block-size=g ~/test #按照单位为g显示

Comment and share

Linux查找含有特定字符串的文件

Linux查找含有特定字符串的文件命令为grep。以下为详细的使用方法

grep

Linux命令:

1
2
3
4
grep [OPTIONS] PATTERN [FILE...]
#实例:递归查找当前文件夹下所有含有test的文件,并显示行号
grep -rn "test" *

参数说明

1
2
3
4
5
6
7
8
9
10
11
-r 递归查找
-n 显示行号
-i 忽略大小写
-w 只匹配整个单词,而不是字符串的字部分(如pattern为"test", 不匹配"test1"或"atest")
-C num 显示匹配到的行上下n行
-l 只列出匹配成功的文件名
-L 列出不匹配的文件名
\< 标注单词的开头
\> 标注单词的结尾
^ 指匹配字符串在行首
$ 指匹配字符串在行尾

常用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#显示匹配pattern1或pattern2的行
grep pattern1 | pattern2 file
# 显示即匹配pattern1又匹配pattern2的行
grep pattern1 file | grep pattern2
#只匹配test或testA,不匹配Atest
grep "\<man" *
#只匹配test或Atest,不匹配testA
grep "man\>" *
#只匹配test 不匹配testA和Atest
grep "\<test\>" *
#匹配行首为test的行:
grep -rn "^test" *
#匹配行尾为test的行
grep -rn "test$" *

Comment and share

C中编码问题

in C++学习

C中编码问题

编码介绍

在代码中我们通常不可避免的出现一些中文,这个时候我们就要考虑到中文的编码格式,如果不注意可能会导致乱码或者信息失真等问题。我们常用的中文编码有GBK,gb2312,Unicode等等。具体详细的介绍看下面几篇文章:

C语言编码转换

在C语言中,如果需要讲编码进行转换,可以使用iconv系列函数。
头文件以及常用函数:

1
2
3
4
5
6
7
8
#include <iconv.h>
typedef void* iconv_t;
extern iconv_t iconv_open(const char* to_code, const char* from_code);
extern size_t iconv(iconv_t cd, char** restrict inbuf, size_t* in_left_buf, char** restrict outbuf, size_t* out_left_buf);
extern int iconv_close(iconv_t cd);

iconv_open

函数说明

此函数说明将要进行哪两种编码的转换,并返回一个转化句柄。

参数说明

  • tocode:目标编码
  • fromcode : 原编码

iconv

1
extern size_t iconv(iconv_t cd, char** restrict inbuf, size_t* in_left_buf, char** restrict outbuf, size_t* out_left_buf);

函数说明

此函数用于从inbuf中读取数据并将转换到指定编码的的数据输出到outbuf中,若转换成功,则输出本次转化的字节数,否则返回sizeof_t(-1)

参数说明

  • cd : 转换描述符,由iconv_open获得
  • inbuf:输入缓冲区
  • in_left_buf :输入缓冲区还未转换的字符数
  • outbuf : 输出缓冲区
  • out_len_buf:输出缓冲区的剩余空间.

iconv_close

1
extern int iconv_close(iconv_t cd);

用于关闭iconv_open打开的文件描述符

举例转换函数

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
#include <iostream>
#include <string>
#include <iconv.h>
#include <cstring>
#include <errno.h>
using namespace std;
string convertCode(const string& p_str, const char* from, const char* to) {
char * sin, * sout;
int lenin, lenout, ret;
const int BUF_LEN = 10240;
char bufOut[BUF_LEN];
string result("");
memset(bufOut, 0x0, sizeof(bufOut));
iconv_t cd;
if ((cd = iconv_open(to, from)) == (iconv_t)(-1)) {
std::cout << "open iconv error" << std::endl;
return "";
}
lenin = p_str.length();
lenout = BUF_LEN;
sin = (char*)p_str.c_str();
sout = bufOut;
// std::cout << sin << std::endl;
//std::cout << lenin << std::endl;
//std::cout << lenout << std::endl;
ret = iconv(cd, &sin, static_cast<size_t * >(&lenin), &sout, static_cast<size_t * >(&lenout));
//errno:84:Invalid or incomplate multibyte or wide character
if (-1 == ret) {
std::cout << strerror(errno) << std::endl;
if (errno != 84) {
return "";
}
}
std::cout << "bufout:" << bufOut << std::endl;
std::cout << "bufout end" << std::endl;
iconv_close(cd);
result.assign(bufOut, BUF_LEN - lenout);
return result;
}
int main() {
string s = "哈哈";
std::cout << s.length() << std::endl;
s = convertCode(s, "gbk", "utf-8//IGNORE");
//std::cout << s << std::endl;
std::cout << s.length() << std::endl;
}

iconv函数出现段错误的原因

使用iconv函数进行转换的时候可能会出现段错误,这里出现这个错误的主要原因是注意看iconv函数的函数原型:

1
extern size_t iconv(iconv_t cd, char** restrict inbuf, size_t* in_left_buf, char** restrict outbuf, size_t* out_left_buf);

长度为size_t的指针,int指针转换为size_t指针在一些系统的转换过程会出现问题,导致长度出现错误,内存越界,出现段错误。错误信息如下:

1
2
3
4
5
Program received signal SIGSEGV, Segmentation fault.
from_gbk (irreversible=0x7fffffffb188, outend=0x61d7c0 "", outptrp=<synthetic pointer>,
inend=0xa7ffffffdb76 <error: Cannot access memory at address 0xa7ffffffdb76>,
inptrp=0x7fffffffb2e8, step_data=0x6157d0, step=0x615030) at ../iconv/loop.c:325
325 ../iconv/loop.c: No such file or directory.

size_t与int类型

size_t类型是在stddef.h文件中定义。size_t的类型与操作系统相关,在32位架构中被普遍定义为:

1
typedef unsigned int size_t;

在64为机器中被定义为:

1
typedef unsigned long size_t;

int类型在32和64为机器上的长度都是4位,long在32位机器为4位,在64位机器为8位。所以在64为机器上,size_t和int指针转换的过程中一定会出现问题,在32为系统中的正整数指针不会指针,但是负整数也会出现问题。

Comment and share

C++中find_if查找vector中的特定struct以及值域查询

由于struct不是C++中的内置类型所以不能用std::find直接查找,而且find函数不能满足值域的查询。这个时候需要使用find_if来进行查询。

find_if函数

find_if是一个模板函数,函数原型及函数定义:

1
2
3
4
5
template <class InputIterator, class Predicate>
InputIterator find_if(InputIterator first, InputIterator end, Predicate pred) {
while (first != last && !pred(*first)) ++first;
return first;
}

函数参数

  • first : 起始迭代器
  • end : 结束迭代器
  • pred : 用于比较数值的函数或者函数对象(仿函数)。遍历条件即为pred()为真.

函数返回值

若有满足pred条件的元素,返回该元素的迭代器,否则返回end.

函数说明

该函数最重要的环节是pred,它的核心环节是重载()运算符,因为每个容器迭代器的*运算符得到的结果都是该容器的value_type的值,所以改重载函数的参数类型是value_type的引用类型

find_if函数应用

在struct的vector中查找特定的对象.特别注意的是:仿函数的参数类型是值的const引用,但是finder的构造参数是实际要比较的值的类型,在使用过程中,向构造函数中传的值是要比较的值。

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
#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;
struct book{
int m_iID;
string m_strName;
book(int t_id, string t_name):m_iID(t_id), m_strName(t_name) {}
};
struct book_finder{
int m_iID;
book_finder(int t_id):m_iID(t_id) {}
bool operator() (const book& t) {return t.m_iID == m_iID;}
};
int main() {
vector<book> bookVc;
book book1(0, "书0");
book book2(1, "书1");
book book3(2, "书2");
book book4(3, "书3");
bookVc.push_back(book1);
bookVc.push_back(book2);
bookVc.push_back(book3);
bookVc.push_back(book4);
book target(1, "书");
if (std::find_if(bookVc.begin(), bookVc.end(), book_finder(target.m_iID)) != bookVc.end()) {
cout << "存在1" << std::endl;
} else {
cout << "不存在1" << std::endl;
}
target.m_iID = 10;
if (std::find_if(bookVc.begin(), bookVc.end(), book_finder(target.m_iID)) != bookVc.end()) {
cout << "存在10" << std::endl;
} else {
cout << "不存在10" << std::endl;
}
}

Comment and share

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China