您现在的位置是: 首页  >  计算机基础  >  操作系统
  • 进程间通信的几种方式

    进程间通信的几种方式

    每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。**进程间通信(IPC,InterProcessCommunication)**是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、信号、共享内存、Socket这6种情况。其中Socket支持不同主机上的两个进程IPC。1、管道1.1无名管道管道,通常指无名管道,是UNIX系统IPC最古老的形式。如果你学过Linux命令,那你肯定很熟悉「|」这个竖线上面命令行里的「|」竖线就是一个管道,它的功能是将前一个命令ps-ef的输出,作为后一个命令grepmysql的输入,从这功能描述,可以看出管道传输数据是单向的,如果想相互通信,我们需要创建两个管道才行。同时,我们得知上面这种管道是没有名字,所以「|」表示的管道称为匿名管道,用完了就销毁。无名管道具有以下特点:它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。1.2FIFO管道还有另外一个类型是命名管道,也被叫做FIFO,因为数据是先进先出的传输方式。FIFO具有以下特点:FIFO可以在无关的进程之间交换数据,与无名管道不同。FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。在使用命名管道前,先需要通过mkfifo命令来创建,并且指定管道名字:fifo1就是这个管道的名称,基于Linux一切皆文件的理念,所以管道也是以文件的方式存在,我们可以用ls看一下,这个文件的类型是p,也就是pipe(管道)的意思:接下来,我们往fifo1这个管道写入数据:你操作了后,你会发现命令执行后就停在这了,这是因为管道里的内容没有被读取,只有当管道里的数据被读完后,命令才可以正常退出。于是,我们执行另外一个命令来读取这个管道里的数据:可以看到,管道里的内容被读取出来了,并打印在了终端上,另外一方面,echo那个命令也正常退出了。我们可以看出,管道这种通信方式效率低,不适合进程间频繁地交换数据。当然,它的好处,自然就是简单,同时也我们很容易得知管道里的数据已经被另一个进程读取了。1.3、管道的原理匿名管道的创建,需要通过下面这个系统调用:当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:其实,所谓的管道,就是内核里面的一串缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。2、消息队列前面说到管道的通信方式是效率低的,因此管道不适合进程间频繁地交换数据。对于这个问题,消息队列的通信模式就可以解决。比如,A进程要给B进程发送消息,A进程把数据放在对应的消息队列后就可以正常返回了,B进程需要的时候再去读取数据就可以了。同理,B进程要给A进程发送消息也是如此。再来,消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在,而前面提到的匿名管道的生命周期,是随进程的创建而建立,随进程的结束而销毁。消息这种模型,两个进程之间的通信就像平时发邮件一样,你来一封,我回一封,可以频繁沟通了。但邮件的通信方式存在不足的地方有两点,一是通信不及时,二是附件也有大小限制,这同样也是消息队列通信不足的点。消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。在Linux内核中,会有两个宏定义MSGMAX和MSGMNB,它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度。消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。消息队列特点消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。3、共享内存消息队列的读取和写入的过程,都会有发生用户态与内核态之间的消息拷贝过程。那共享内存的方式,就很好的解决了这一问题。现代操作系统,对于内存管理,采用的是虚拟内存技术,也就是每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中。所以,即使进程A和进程B的虚拟地址是一样的,其实访问的是不同的物理内存地址,对于数据的增删查改互不影响。共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。共享内存特点共享内存是最快的一种IPC,因为进程是直接对内存进行存取。因为多个进程可以同时操作,所以需要进行同步。信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。4、信号量用了共享内存通信方式,带来新的问题,那就是如果多个进程同时修改同一个共享内存,很有可能就冲突了。例如两个进程都同时写一个地址,那先写的那个进程会发现内容被别人覆盖了。为了防止多进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。正好,信号量就实现了这一保护机制。信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。信号量表示资源的数量,控制信号量的方式有两种原子操作:一个是P操作,这个操作会把信号量减去-1,相减后如果信号量<0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量>=0,则表明还有资源可使用,进程可正常继续执行。另一个是V操作,这个操作会把信号量加上1,相加后如果信号量<=0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量>0,则表明当前没有阻塞中的进程;P操作是用在进入共享资源之前,V操作是用在离开共享资源之后,这两个操作是必须成对出现的。接下来,举个例子,如果要使得两个进程互斥访问共享内存,我们可以初始化信号量为1。具体的过程如下:进程A在访问共享内存前,先执行了P操作,由于信号量的初始值为1,故在进程A执行P操作后信号量变为0,表示共享资源可用,于是进程A就可以访问共享内存。若此时,进程B也想访问共享内存,执行了P操作,结果信号量变为了-1,这就意味着临界资源已被占用,因此进程B被阻塞。直到进程A访问完共享内存,才会执行V操作,使得信号量恢复为0,接着就会唤醒阻塞中的线程B,使得进程B可以访问共享内存,最后完成共享内存的访问后,执行V操作,使信号量恢复到初始值1。可以发现,信号初始化为1,就代表着是互斥信号量,它可以保证共享内存在任何时刻只有一个进程在访问,这就很好的保护了共享内存。另外,在多进程里,每个进程并不一定是顺序执行的,它们基本是以各自独立的、不可预知的速度向前推进,但有时候我们又希望多个进程能密切合作,以实现一个共同的任务。例如,进程A是负责生产数据,而进程B是负责读取数据,这两个进程是相互合作、相互依赖的,进程A必须先生产了数据,进程B才能读取到数据,所以执行是有前后顺序的。那么这时候,就可以用信号量来实现多进程同步的方式,我们可以初始化信号量为0。具体过程:如果进程B比进程A先执行了,那么执行到P操作时,由于信号量初始值为0,故信号量会变为-1,表示进程A还没生产数据,于是进程B就阻塞等待;接着,当进程A生产完数据后,执行了V操作,就会使得信号量变为0,于是就会唤醒阻塞在P操作的进程B;最后,进程B被唤醒后,意味着进程A已经生产了数据,于是进程B就可以正常读取数据了。可以发现,信号初始化为0,就代表着是同步信号量,它可以保证进程A应在进程B之前执行。5、信号上面说的进程间通信,都是常规状态下的工作模式。对于异常情况下的工作模式,就需要用「信号」的方式来通知进程。信号跟信号量虽然名字相似度66.66%,但两者用途完全不一样,就好像Java和JavaScript的区别。在Linux操作系统中,为了响应各种各样的事件,提供了几十种信号,分别代表不同的意义。我们可以通过kill-l命令,查看所有的信号:运行在shell终端的进程,我们可以通过键盘输入某些组合键的时候,给进程发送信号。例如Ctrl+C产生SIGINT信号,表示终止该进程;Ctrl+Z产生SIGTSTP信号,表示停止该进程,但还未结束;如果进程在后台运行,可以通过kill命令的方式给进程发送信号,但前提需要知道运行中的进程PID号,例如:kill-91050,表示给PID为1050的进程发送SIGKILL信号,用来立即结束该进程;所以,信号事件的来源主要有硬件来源(如键盘Cltr+C)和软件来源(如kill命令)。信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程,一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式。1.执行默认操作。Linux对每种信号都规定了默认操作,例如,上面列表中的SIGTERM信号,就是终止进程的意思。Core的意思是CoreDump,也即终止进程后,通过CoreDump将当前进程的运行状态保存在文件里面,方便程序员事后进行分析问题在哪里。2.捕捉信号。我们可以为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。3.忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即SIGKILL和SEGSTOP,它们用于在任何时候中断或结束某一进程。6、socket前面提到的管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,那要想跨网络与不同主机上的进程之间通信,就需要Socket通信了。实际上,Socket通信不仅可以跨网络与不同主机的进程间通信,还可以在同主机上进程间通信。关于Linux的Socket可以参考我的另一篇博文Linux之Socket编程详解

    LoveIT 2020-09-16
    操作系统
  • 操作系统-缓存算法(页面置换算法)

    操作系统-缓存算法(页面置换算法)

    缓存算法是指令的一个明细表,用于提示计算设备的缓存信息中哪些条目应该被删去。常见缓存算法包括FIFO、LFU、LRU、ARC、MRU。1、FIFOFIFO(FirstinFirstout),先进先出。其实在操作系统的设计理念中很多地方都利用到了先进先出的思想,比如作业调度(先来先服务),为什么这个原则在很多地方都会用到呢?因为这个原则简单、且符合人们的惯性思维,具备公平性,并且实现起来简单,直接使用数据结构中的队列即可实现。在FIFOCache设计中,核心原则就是:如果一个数据最先进入缓存中,则应该最早淘汰掉。也就是说,当缓存满的时候,应当把最先进入缓存的数据给淘汰掉。在FIFOCache中应该支持以下操作:那么利用什么数据结构来实现呢?下面提供一种实现思路:利用一个双向链表保存数据,当来了新的数据之后便添加到链表末尾,如果Cache存满数据,则把链表头部数据删除,然后把新的数据添加到链表末尾。在访问数据的时候,如果在Cache中存在该数据的话,则返回对应的value值;否则返回-1。如果想提高访问效率,可以利用hashmap来保存每个key在链表中对应的位置。2、LRULRU全称是LeastRecentlyUsed,即最近最久未使用的意思。LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。用什么数据结构来实现LRU算法呢?可能大多数人都会想到:用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。这种实现思路很简单,但是有什么缺陷呢?需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。那么有没有更好的实现办法呢?有一种流行的思路是利用链表和哈希表。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部,如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。详细的实现可以参考我的另一篇博文:LRU缓存算法到底是怎么一回事?3、LFULFU(LeastFrequentlyUsed)最近最少使用算法。它是基于“如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小”的思路。LFU和LRU算法的不同之处,LRU的淘汰规则是基于访问时间,而LFU是基于访问次数的。用什么数据结构来实现LFU算法呢?有一种实现方式是使用三个HashMap,一个Map维护Key-Value的关系,一个Map维护Key-Freq的关系,前两个Map可以保证查找和插入是O(1)的时间复杂度,不能保证删除也是O(1)的时间复杂度,因此还需要使用一个类似LinkList的数据结构维护插入的时间顺序,并且还需要肯定是需要freq到key的映射,用来找到freq最小的key,因此可以维护一个Map<Integer,LinkedHashSet>类型的Map,这是个一对多的关系,一方就是频率,多方就是key,因为具有某个频率的key可能有多个,并且在使用一个变量minFreq维护系统当前访问频次最低的次数,当表满了的时候直接删除minFreq对应的LinkedHashSet中的第一个key就好了。详细的代码实现可以参考我的另一篇博文:LFU缓存算法到底是怎么一回事?4、自适应缓存替换算法(ARC)由IBMAlmaden研究中心开发,这个缓存算法同时跟踪记录LFU和LRU,以及驱逐缓存条目,来获得可用缓存的最佳使用。5、最近最常使用算法(MRU)这个缓存算法最先移除最近最常使用的条目。一个MRU算法擅长处理一个条目越久,越容易被访问的情况。参考【1】Matrix海子.缓存算法(页面置换算法)-FIFO、LFU、LRU.博客园【2】JCjunior.操作系统之缓存算法.CSDN

    LoveIT 2020-09-01
    操作系统
  • LFU缓存算法到底是怎么一回事?

    LFU缓存算法到底是怎么一回事?

    1、LFU算法是什么?LFU(LeastFrequentlyUsed)最近最少使用算法。它是基于“如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小”的思路。LFU和LRU算法的不同之处,LRU的淘汰规则是基于访问时间,而LFU是基于访问次数的。从实现难度上来说,LFU算法的难度大于LRU算法,因为LRU算法相当于把数据按照时间排序,这个需求借助链表很自然就能实现,你一直从链表头部加入元素的话,越靠近头部的元素就是新的数据,越靠近尾部的元素就是旧的数据,我们进行缓存淘汰的时候只要简单地将尾部的元素淘汰掉就行了。而LFU算法相当于是淘汰访问频次最低的数据,如果访问频次最低的数据有多条,需要淘汰最旧的数据。把数据按照访问频次进行排序,而且频次还会不断变化,这可不容易实现。2、实现LFU算法LFU常见的实现方式有以下三种:(1)利用一个数组存储数据项,用hashmap存储每个数据项在数组中对应的位置,然后为每个数据项设计一个访问频次,当数据项被命中时,访问频次自增,在淘汰的时候淘汰访问频次最少的数据。这样一来的话,在插入数据和访问数据的时候都能达到O(1)的时间复杂度,在淘汰数据的时候,通过选择算法得到应该淘汰的数据项在数组中的索引,并将该索引位置的内容替换为新来的数据内容即可,这样的话,淘汰数据的操作时间复杂度为O(n)。(2)利用小顶堆+哈希表,小顶堆插入、删除操作都能达到O(logn)时间复杂度,因此效率相比第一种实现方法更加高效。(3)使用三个HashMap,一个Map维护Key-Value的关系,一个Map维护Key-Freq的关系,前两个Map可以保证查找和插入是O(1)的时间复杂度,不能保证删除也是O(1)的时间复杂度,因此还需要使用一个类似LinkList的数据结构维护插入的时间顺序,并且还需要肯定是需要freq到key的映射,用来找到freq最小的key,因此可以维护一个Map<Integer,LinkedHashSet>类型的Map,这是个一对多的关系,一方就是频率,多方就是key,因为具有某个频率的key可能有多个,并且在使用一个变量minFreq维护系统当前访问频次最低的次数,当表满了的时候直接删除minFreq对应的LinkedHashSet中的第一个key就好了,这样就可以实现O(1)的删除时间复杂度接下来我们针对第三个方案,来实现一下LFU,首先我们分析一下算法的实现思路思路分析先从最简单的开始,根据LFU算法的逻辑,我们先列举出算法执行过程中的几个显而易见的事实:1、调用get(key)方法时,要返回该key对应的val。对于这个需求,我们可以使用一个Map来保存Key-Value的映射关系,可以使get()方法是O(1)的时间复杂度。2、只要用get或者put方法访问某个key一次,该key的freq就要加1。对于这个需求,还是可以使用一个Map来保存Key-Freq的映射关系3、如果在容量满了的时候进行插入,则需要将freq最小的key删除,如果最小的freq对应多个key,则删除其中最旧(加入时间最长)的那一个数据。这种情况比较复杂,也是实现LFU的核心,我们分开说:3.1为了通过freq找到需要删除的key,我们首先需要一个Map来维护freq到key的映射关系;3.2将freq最小的key删除,那你就得快速得到当前所有key最小的freq是多少。想要时间复杂度O(1)的话,肯定不能遍历一遍去找,那就用一个变量minFreq来记录当前最小的freq吧。3.3可能有多个key拥有相同的freq,所以freq对key是一对多的关系,即一个freq对应一个key的列表。3.4希望freq对应的key的列表是存在时序的,便于快速查找并删除最旧的key。3.5希望能够快速删除key列表中的任何一个key,因为如果频次为freq的某个key被访问,那么它的频次就会变成freq+1,就应该从freq对应的key列表中删除,加到freq+1对应的key的列表中。综合各种情况,我们可以使用Map<Integer,LinkedHashSet>来解决问题,LinkedHashSet顾名思义,是链表和哈希集合的结合体。链表不能快速访问链表节点,但是插入元素具有时序;哈希集合中的元素无序,但是可以对元素进行快速的访问和删除。那么,它俩结合起来就兼具了哈希集合和链表的特性,既可以在O(1)时间内访问或删除其中的元素,又可以保持插入的时序。综上,我们可以大致写一下LRU算法大致算法框架:接下来就是实现get和put两个核心功能,get方法的执行逻辑非常简单,就是直接从K-V哈希表中获取键位key的元素即可,如果有就返回Value并同时更新对应key的访问频次即可;否则返回null,结束。updateFreq方法更新某个key的freq肯定会涉及FK表和KF表,我们分别更新这两个表就行了。和之前类似,当FK表中freq对应的列表被删空后,需要删除FK表中freq这个映射。如果这个freq恰好是minFreq,说明minFreq变量需要更新。能不能快速找到当前的minFreq呢?这里是可以的,因为我们刚才把key的freq加了1嘛,所以minFreq也加1就行了。下面来实现put(key,val)方法,逻辑略微复杂,我们直接画个图来看:removeMinFreqKey方法中删除某个键key肯定是要同时修改三个映射表的,借助minFreq参数可以从FK表中找到freq最小的keyList,根据时序,其中第一个元素就是要被淘汰的deletedKey,操作三个映射表删除这个key即可。但是有个细节问题,如果keyList中只有一个元素,那么删除之后minFreq对应的key列表就为空了,也就是minFreq变量需要被更新。如何计算当前的minFreq是多少呢?实际上没办法快速计算minFreq,只能线性遍历FK表或者KF表来计算,这样肯定不能保证O(1)的时间复杂度。但是,其实这里没必要更新minFreq变量,因为你想想removeMinFreqKey这个函数是在什么时候调用?在put方法中插入新key时可能调用。而你回头看put的代码,插入新key时一定会把minFreq更新成1,所以说即便这里minFreq变了,我们也不需要管它。3、总结终于总结完了,其实,感觉思想搞明白了,代码实现起来就相对容易一些。但是,还是需要多写,多实践。过段时间再来回顾一下下面将源码贴出来:github:https://github.com/LoverITer/leetcode/blob/master/lru/src/main/java/cache/lfu/LFUCache.java完整代码:参考【1】labuladon.算法题就像搭乐高:手把手带你拆解LFU算法.微信公众号

    LoveIT 2020-09-01
    操作系统
  • LRU缓存算法到底是怎么一回事?

    LRU缓存算法到底是怎么一回事?

    1、LRU算法是什么?​LRU:LeastRecentlyUsed,即最近最久未使用的意思。​LRU算法是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。​该算法是计算机操作系统中置换页的一种算法,同时在其他领域也有广泛应用,比如Redis的内存淘汰策略,该算法也是面试中面试官常常用来考验面试者代码能力和对LRU算法的正确理解。2、实现LRU算法​理论上,LRU有以下三种实现方式:​(1)用一个数组来保存数据,并给每一个数据标记一个访问时间戳。每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。​(2)利用一个链表来实现。每次新插入数据的时候将新数据插到链表的头部;每次缓存命中(即数据被访问),则将数据移到链表头部;那么当链表满的时候,就将链表尾部的数据丢弃。​(3)利用双向链表和哈希表。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部并返回值;如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。​对于第一种方法,需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。对于第二种方法,链表在定位数据的时候时间复杂度为O(n)。所以在一般使用第三种方式来是实现LRU算法。接下来我就使用HashMap+双链表的方式手撕一个时间复杂度为O(1)的LRU算法。Tips:在jdk中,LinkedHashMap其实已经实现了LRU缓存淘汰算法需要在构造方法第三个参数传入true(accessOrder=true;),表示按照时间顺序访问。可以直接继承LinkedHashMap来实现。LinkedHashMap中本身就实现了一个方法removeEldestEntry用于判断是否需要移除最不常读取的数,方法默认是直接返回false,不会移除元素,所以需要重写该方法。即当缓存满后就移除最不常用的数。下图所示是一个基于LinkedHashMap实现LRU的实例:手撕LRU算法思路:使用HashMap保存每个数据项的Key,通过Key就能以O(1)的时间得到节点;访问某个结点的时候就将其从原来位置删除,并重新插入到链表头部。这样就能保证链表尾部存储的就是最近最久未使用的节点,当节点数量大于缓存最大空间时就淘汰链表尾部的节点。由于使用HashMap保存了Key到节点的映射,因此通过Key就能以O(1)的时间得到节点,然后双链表可以保证以O(1)的时间将其从链表中删除(不用单链表的原因)。Java实现如下:(1)定义基本的链表操作节点(2)双向链表定义我们定义一个LRUCache类,然后定义它的容量、头节点、尾节点以及用一个HashMap来映射key和各个结点,然后一个基本的构造方法初始化。接下来就是几个操作链表结点的方法:删除:remove()、添加:put()、获取:get()、移动到头部:moveToHead()。(3)添加元素添加元素的时候首先判断是不是新的元素,如果是新元素,判断当前的大小是不是大于总容量了,防止超过总链表大小,如果大于的话直接抛弃最后一个节点,然后再以传入的key\value值创建新的节点。对于已经存在的元素,直接覆盖旧值,再将该元素移动到头部,然后保存在map中(4)访问元素通过key值来访问元素,主要的做法就是先从Map中根据给定的key判断如果是不存在的,直接返回null。如果存在,把数据移动到首部头节点,然后再返回旧值。(5)节点删除操作在根据key删除节点的操作中,我们需要做的是把节点的前一个节点的指针指向当前节点下一个位置,再把当前节点的下一个的节点的上一个指向当前节点的前一个,这么说有点绕,我们来画图来看:(6)移动元素到头节点首先把当前节点移除,类似于删除的效果(但是没有移除该元素),然后再将首节点设为当前节点的下一个,再把当前节点设为头节点的前一个节点。当前几点设为首节点。再把首节点的前一个节点设为null,这样就是间接替换了头节点为当前节点。3、测试代码写完了,我们来测试一下结果:执行结果:可以看到,越是后添加的结点也是靠近链表头部,越是靠近尾部的元素越是优先被淘汰,这符合LRU算法的思想。4、总结本文主要讲述了LRU的算法实现,理解了LRU也能帮助我们理解Java中的LinkedList,因为LinkedList本身就是双向链表。还有就是理解数据结构这种方式,以及LRU的移动节点的过程,如果能在实际的开发中利用它的特性使用到合适的业务场景中。附:LRU算法完整代码

    LoveIT 2020-07-07
    操作系统
  • Linux之Socket编程详解

    Linux之Socket编程详解

    一、网络中进程之间如何通信进程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如:UNIXBSD有:管道(pipe)、命名管道(namedpipe)软中断信号(signal)UNIXsystemV有:消息(message)、共享存储区(sharedmemory)和信号量(semaphore)等.他们都仅限于用在本机进程之间通信。网间进程通信要解决的是不同主机进程间的相互通信问题(可把同机进程通信看作是其中的特例)。为此,首先要解决的是网间进程标识问题。同一主机上,不同进程可用进程号(processID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。例如,主机A赋于某进程号5,在B机中也可以存在5号进程,因此,“5号进程”这句话就没有意义了。其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。使用TCP/IP协议的应用程序通常采用应用编程接口:UNIXBSD的套接字(socket)和UNIXSystemV的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。二、什么是socket1、socket套接字socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open–>读写write/read–>关闭close”模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭).说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。注意:其实socket也没有层的概念,它只是一个facade设计模式的应用,让编程变的更简单。是一个软件抽象层。在网络编程中,我们大量用的都是通过socket实现的。2、套接字描述符套接字描述符和文件描述符的概念类似,都是用于标识打开的文件的,只不过这里特殊点,是用来标识建立的socket。它是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE*结构的表示就是stdin、stdout、stderr三、Linux内核提供的socket编程系统调用1、socket()函数原型:​函数执行后返回sockfd,这是一个文件描述符。socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而**socket()**用于创建一个socket描述符(socketdescriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。​创建socket的时候,可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:(1)domain:即协议域,又称为协议族(family)。常用的协议族有:AF_INET:表示使用IPV4AF_INET6:表示使用IPV6AF_LOCAL(或称AF_UNIX,Unix域socket):表示使用绝对路径名作为地址AF_ROUTE:协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。(2)type:指定socket类型。常用的socket类型有:SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET(3)protocol:故名思意,就是指定协议。常用的协议有:IPPROTO_TCP:使用TCP传输协议IPPTOTO_UDP:使用UDP传输协议IPPROTO_SCTP:使用STCP传输协议IPPROTO_TIPC:使用TIPC传输协议​当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(addressfamily,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。2、bind()函数原型:bind()函数把一个地址族中的特定地址绑定到一个socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合绑定到socket。函数的三个参数分别为:(1)sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。(2)addr:一个结构体指针,用于指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同。这里如果想在Linux下实现socket编程的话,需要对ipv4或ipv6的结构非常了解才可以。ipV4的结构:ipv6的结构:(3)addrLen:对应的是地址的长度。通常服务器在启动的时候都会绑定一个地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。主机字节序和网络字节序的转换主机字节序:就是我们平常说的大端和小端模式,不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。网络字节序:4个字节的32bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。**由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。**字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。所以,在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。解决方法使用htons()将短整型的主机字节序转化为网络字节序、htonl()将长整型的主机字节序转化为网络字节序使用ntohs()将短整型的网络字节序转化为主机字节序、ntohl()将长整型的网络字节序转化为主机字节序3、listen()、connect()当服务器在调用socket()创建并使用bind()绑定ip和端口号到一个socket通道之后,接下来就需要监听客户端的连接请求了,Linux为我们提供了listen()系统调用,他的函数原型如下:函数的第一个参数即为要监听的socket的文件描述符(可以通过socket()调用获得),第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。在主句准备好并处于监听之中时,客户端就可以使用connect()来连接服务器,他的函数原型如下:函数的第一个参数即为客户端的socket的文件描述符,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。4、accept()TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。他的函数原型如下:他有3个参数:(1)sockfd:参数sockfd就是上面解释中的监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。(2)addr:这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。(3)addrlen:用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。如果accept成功返回(返回0),则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。注意!accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。此时我们需要区分两种套接字:监听套接字和连接套接字​监听套接字:监听套接字正如accept的参数sockfd,它是监听套接字,在调用listen函数之后,是服务器开始调用socket()函数生成的,称为监听socket描述字(监听套接字)​连接套接字:一个套接字会从主动连接的套接字变身为一个监听套接字;而accept函数返回的是已连接socket描述字(一个连接套接字),它代表着一个网络已经存在的点点连接。​一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。5、read()、write()等函数至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!Linux提供的网络I/O操作有下面几组:read()/write()recv()/send()readv()/writev()recvmsg()/sendmsg()recvfrom()/sendto()它们的声明分别如下:(1)read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。(2)write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置errno变量。在网络程序中,当我们向套接字文件描述符写时有俩种可能1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。6、close()在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。关闭一个TCPsocket的缺省行为是把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。四、Linux下socket编程示例服务器端:启动后监听9000端口,如果收到连接请求,将接收请求并接收客户端发来的消息,并向客户端返回消息。客户端:连接服务器,并向服务器发送消息测试:首先编译源文件,为了方便管理我这里编写一个Makefile执行Makefile编译生成可执行性文件之后首先启动server:之后在另一个窗口启动客户端并向服务器发送消息,服务器也把消息返送回来:转载来自:https://www.cnblogs.com/jiangzhaowei/p/8261174.html

    LoveIT 2020-06-06
    Linux
  • Linux sort、uniq、cut、wc命令详解

    Linux sort、uniq、cut、wc命令详解

    一、sort命令sort命令对指定的文件中的行排序,并将结果写到标准输出。如果指定多个文件,那么sort命令将这些文件连接起来,并当作一个文件进行排序。1、sort命令语法:常用可选OPTION:选项参数作用-f忽略大小写的差异,例如A与a视为相同-b忽略最前面的空格符部分-M以月份的名字来排序,例如JAN,DEC等等的排序方法-n使用『纯数字』进行排序(默认是以文字型态来排序的)-r反向排序-u就是uniq,相同的数据中,仅出现一行代表-t指定分隔符,默认使用Tab分隔-k以那个区间(field)来进行排序的意思2、sort基本用法示例对/ect/passwd中的账户排序sort默认对字符串排序的,排序安字符串每个字符在字典中出现的先后升序排列,因此在结果中我们结果由字母a开始升序排序。/etc/passwd的第三行都是数字,我想以第三行的数字对账户进行排序默认是升序排列,如果要降序排序,如下:以/etc/passwd的第7个字段进行排序并去重,以统计出有多少种Shell二、uniq命令uniq命令可以去除排序过的文件中的重复行,因此uniq经常和sort合用。也就是说,为了使uniq起作用,所有的重复行必须是相邻的。1、uniq命令语法常用可选OPTION:选项参数作用-i忽略大小写字符的不同-c进行计数-u仅显示出一次的行列-d仅显示重复出现的行列-w指定要比较的字符2、uniq命令基本用法示例uniq处理数据是按行处理的,只有一行都相同的时候才会合并,如果一行中只有一部分相同则会认为是不同的。在根目录下有一个文件uniq.txt,对这个文件排序并去重排序之后删除重复行,同时在行首位置输出该行重复的次排序并去重后仅显示重复的行排序后仅仅显示没有重复的行三、cut命令cut可以从一个文本或文本列中提取文本列cut命令语法:常用可选OPTION:选项参数作用-d后面接分隔字符。与-f一起使用-f依据-d的分隔字符将一段信息分割成为数段,用-f取出第几段的意思-c以字符(characters)的单位取出固定字符区间-b以字节为单位进行分割。这些字节位置将忽略多字节字符边界,除非也指定了-n标志-n取消分割多字节字符。仅和-b标志一起使用-d和-f参数配合使用类似可以达到类似awk命令的功能,例如下面这个例子,将系统环境变量PATH取出获得其中的任意一个路径将系统环境变量PATH取出获得其中第1~3个路径只显示/etc/passwd中的用户名和Shell类型四、wc命令利用wc指令我们可以计算文件的字节大小、字数、或是列数,若不指定文件名称、或是所给予的文件名为"-",则wc指令会从标准输入设备读取数据。wc命令语法:常用可选OPTION:选项参数作用-l只显示行数-w只显示字数-m只显示有多少字符-c只显示Bytes数wc命令的使用比较简单,默认使用wc不指定任何参数的时候会输出行数,单词数和字节数打印出三个数字,从左到右依次表示输入所包含的行数、单词数和字节大小当我们只想获得文件的某个方面的参数的时候可以指定对应的选项参数:

    LoveIT 2020-05-22
    Linux
  • Linux文本处理cat、head、tail、grep、sed和awk详解

    Linux文本处理cat、head、tail、grep、sed和awk详解

    Linux哲学思想中有一条就是Linux下一切皆文件。因此当我们操作Linux的时候,本质是在操作各种文件。因此掌握文本编辑的命令也是至关重要的,Linux提供了很多的文本编辑命令,以及被称为Linux"三剑客“的grep、sed和awk命令。一、cat命令cat命令(concatenate(连接、连续)的简写)用于连接文件并打印到标准输出设备上,也可以把几个文件内容附加到另一个文件中,即连接合并文件。cat命令的语法格式如下可用的选项:选项含义-A相当于-vET选项的整合,用于列出所有隐藏符号;-E列出每行结尾的回车符$;-n对输出的所有行进行编号;-b和-n参数作用类似,但此选项表示只对非空行进行编号。-T把Tab键^I显示出来;-v列出特殊字符;-s当遇到有连续2行以上的空白行时,就替换为1行的空白行。示例把a.txt中的内容加上行号复制一份到b.txt合并a.txt和b.txt中的内容,并不要空格加上行号之后追加到c.txt二、head命令head命令可以显示指定文件前若干行的文件内容,其基本格式如下:常用可用的选项:选项含义-nK这里的K表示行数,该选项用来显示文件前K行的内容;如果使用"-K"作为参数,则表示除了文件最后K行外,显示剩余的全部内容。-cK这里的K表示字节数,该选项用来显示文件前K个字节的内容;如果使用"-K",则表示除了文件最后K字节的内容,显示剩余全部内容。-v显示文件名;注意!如果没有指定具体显示的行数,默认显示就是前10行。下图是使用head命令查看tomcat中官方README.md的打印:三、tail命令tail命令的作用和head命令的作用正好相反,tail它可以从文件末尾查看文件内容。命令格式如下:常用可用选项:选项含义-nK这里的K指的是行数,该选项表示输出最后K行,在此基础上,如果使用-n+K,则表示从文件的第K行开始输出(这个参数在过滤文本表头的时候非常有用)。--cK这里的K指的是字节数,该选项表示输出文件最后K个字节的内容,在此基础上,使用-c+K则表示从文件第K个字节开始输出。-f输出文件变化后新增加的数据。同样地,tail命令如果在使用的时候没有指定行数,那么默认显示最后10行。使用cat、head、tail组合1、查看日志pro.log后100行数据2、查看日志pro.log100到300行的数据3、打印pro.log文件第1000行开始以后的内容4、打印pro.log文件第1000行开始之前的内容四、grep命令很多时候,我们并不需要列出文件的全部内容,而是从文件中找到包含指定信息的那些行,要实现这个目的,可以使用grep命令。grep是globalregularexpressionsprint的简写,它能够在一个或多个文件中,搜索某一特定的字符模式(也就是正则表达式),此模式可以是单一的字符、字符串、单词或句子。grep家族一共有三个:grep、egrep和fgrep。grep在查找的时候,会有三种类似返回值:找到返回0,没找到返回1,没有找到指定文件返回21、grep命令的格式这里的Patterns支持正则表达式以及普通字符串,grep的可选参数众多,按照men对grep提供的帮助,大致可以划分为以下几种:(1)匹配选择选项含义-E开启扩展(Extend)的正则表达式,也就相当于egrep命令-F解释PATTERN作为固定字符串的列表,由换行符分隔,其中任何一个都要匹配。也就相当于使用fgrep。-G将PATTERN视为普通的表示法来使用。这个是默认值,也就是grep-P将PATTERN解释为Perl正则表达式。(2)匹配控制选项选项含义-ePATTERN使用指定字符串做为查找文件内容的模式。-fFILE指定规则文件,其内容含有一个或多个规则样式,让grep查找符合规则条件的文件内容,格式为每行一个规则样式。-i忽略大小写(ignorecase)。-v只打印没有匹配的,而匹配的反而不打印-w被匹配的文本只能是单词,而不能是单词中的某一部分-x只显示全列符合的列。-y此参数的效果和指定"-i"参数相同。(3)输出控制选项含义-c,--count显示总共有多少行被匹配到了,而不是显示被匹配到的内容--color将匹配到的内容以颜色高亮显示。-L列出文件内容不符合指定的样式的文件名称。-l列出文件内容符合指定的样式的文件名称。-o只显示匹配PATTERN部分。-q,--quiet,--silent静默。即使有匹配的内容也不显示出来。-s不显示错误信息。-b在显示符合样式的那一行之前,标示出该行第一个字符的编号-H在显示符合样式的那一行之前,表示该行所属的文件名称。-h在显示符合样式的那一行之前,不标示该行所属的文件名称。-Z,--null输出零字节(ASCIINUL字符),而不是通常在文件名后的字符。例如,grep-lZ在每个文件名之后输出一个零字节,而不是通常的换行符。即使存在包含不寻常字符(例如换行符)的文件名,此选项也可以使输出明确。-n显示行号(4)文本行控制选项含义-ANUM,--after-context=NUM显示匹配到的字符串所在的行及其后n行-BNUM,--before-context=NUM显示匹配到的字符串所在的行及其前n行-CNUM,--context=NUM显示匹配到的字符串所在的行及其前后各n行--group-separator=SEP使用SEP作为组分隔符。默认情况下,SEP是双连字符(-)。---no-group-separator使用空字符串作为组分隔符。2、正则表达式grep命令支持使用正则表达式,正则表达式又有基本正则表达式和扩展正则表达式的区别。(1)基本正则表达式匹配字符:  .:任意一个字符。  [abc]:表示匹配一个字符,这个字符必须是abc中的一个。  [a-zA-Z]:表示匹配一个字符,这个字符必须是a-z或A-Z这52个字母中的一个。  [\^123]:匹配一个字符,这个字符是除了1、2、3以外的所有字符。对于一些常用的字符集,系统也做了定义:  [A-Za-z]等价于[[:alpha:]]  [0-9]等价于[[:digit:]]  [A-Za-z0-9]等价于[[:alnum:]]  tab,space等空白字符[[:space:]]  [A-Z]等价于[[:upper:]]  [a-z]等价于[[:lower:]]  标点符号[[:punct:]]匹配次数:\\{m,n\\}:匹配其前面出现的字符至少m次,至多n次。\?:匹配其前面出现的内容0次或1次,等价于{0,1}。\*:匹配其前面出现的内容任意次,等价于{0,},所以".*"表述任意字符任意次,即无论什么内容全部匹配。匹配多次,在使用的时候\一定不能丢匹配任意次位置锚定:  ^:锚定行首  \$:锚定行尾。技巧:"^$"用于匹配空白行。  \b或\<:锚定单词的词首。如"\blike"不会匹配alike,但是会匹配liker  \b或\>:锚定单词的词尾。如"\blike\b"不会匹配alike和liker,只会匹配like  \B:与\b作用相反。匹配以某个字符(串)开始的行匹配以某个字符(串)结束的行分组及引用:  \\(string\\):将string作为一个整体方便后面引用  \1:引用第1个左括号及其对应的右括号所匹配的内容。  \2:引用第2个左括号及其对应的右括号所匹配的内容。  \n:引用第n个左括号及其对应的右括号所匹配的内容。匹配开头字符和结束字符相同的行(2)扩展正则表达式扩展正则正则表达式需要加-E或者配合egrep使用,基本的东西和基本正则表达式一样,只是扩展了一些内容,具体如下:匹配字符:这部分和基本正则表达式一样匹配次数: \*:和基本正则表达式一样 ?:基本正则表达式是?,二这里没有\。 {m,n}:相比基本正则表达式也是没有了\。比如,{1,3}表示匹配1至3次;{3}表示匹配3次 +:匹配其前面的字符至少一次,相当于{1,}。位置锚定:和基本正则表达式一样。分组及引用: (string):相比基本正则表达式也是没有了\。 \1:引用部分和基本正则表达式一样。 \n:引用部分和基本正则表达式一样。逻辑选择a|b:匹配a或b。比如,/^([0-9]{3}-|([0-9]{3}))/表示匹配xxx-或(xxx)的数字串3、grep基本用法案例其实grep的输入不一定非要是文件,也可以是标准输入或者管道,比如:grep"root",和匹配标准输入使用grep查看指定进程:ps-ef|grep进程名或pa-aux|grep进程名过滤当前目录下的文件夹在网络配置文件/etc/sysconfig/network-scripts/ifcfg-ens33中检索出所有的IPgrep命令的功能非常强大,通过利用它的不同选项以及变化万千的正则表达式,可以获取任何我们所需要的信息。这里所介绍的grep命令,只介绍了它的一部分基础知识,比如说,grep命令可用的选项还有很多,且用法也五花八门,不过对于初学者来说,本节所介绍的内容已经足以应付多数Linux系统的日常工作了。五、sed命令我们知道,Vim采用的是交互式文本编辑模式,你可以用键盘命令来交互性地插入、删除或替换数据中的文本。但这里要讲的sed命令不同,它采用的是流编辑模式,最明显的特点是,sed是一种在线的、非交互式的编辑器,它-次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为模式空间(patternspace),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。sed的工作流程如下图所示:1、sed命令的基本格式2、sed常用的选项参数(sedOPTION)选项含义-e脚本命令该选项会将其后跟的脚本命令添加到已有的命令中。-f脚本命令文件该选项会将其后文件中的脚本命令添加到已有的命令中。一般不推荐使用-n默认情况下,sed会在所有的脚本指定执行完毕后,会自动输出处理后的内容,而该选项会屏蔽启动输出,需使用print命令来完成输出。-i此选项会直接修改源文件,要慎用。-r支持扩展正则表达式特别需要注意的是,sed与grep不同,它的返回值0并不能表示它有没有修改成功,只有当发生语法错误的时候才会返回1。3、sed命令参数(sedCOMMAND)命令参数功能a在定位行号后附加新文本信息i在定位行号后插入新文本信息d删除匹配的行c用新文本替换匹配的文本s使用替换模式替换匹配的文本g对数据中所有匹配到的内容进行全局替换,如果没有g,则只会在第一次匹配成功时做替换操作p打印与替换命令中指定的模式匹配的行。此标记通常与-n选项一起使用。q结束或退出sedn1~512之间的数字,表示指定要替换的字符串出现第几次时才进行替换,例如,一行中有3个A,但用户只想替换第二个A,这是就用到这个标记;r读取文件到缓冲区**w**将缓冲区中的内容写到指定的file文件中h把模式空间中的内容复制到暂存空间H把模式空间中的内容追加到暂存空间x交换模式空间和暂存空间的内容4、sed基本用法sed的命令中支持使用正则表达式,但是用到扩展正则表达式的时候必须结合-r选项,并且正则表达式必须写在双斜线//之间,下面来演示一下sed命令的基本用法:(1)不使用任何COMMANDsed-r""passwd(2)让sed参数静默,不输出内容sed-rn""passwd(3)通过COMMAND让sed输出内容sed-rn"p"passwd(4)使用正则表达式让sed打印root开头的行sed-rn"/^root/p"passwd(5)全局匹配并替换sed-r"s/要替换的串/目标串/g"FILE(6)忽略大小写全局替换sed-r"s/要替换的串/目标串/gi"FILE5、sed扩展(高级)用法5.1sed定址操作地址用于决定对哪些行进行编辑。地址形式可以是数字、正则表达式或二者的结合。如果没有指定地址,sed将处理输入文件中的所有行。删除命令:d查找命令:s文件读入命令:r文件写出命令:w追加、插入、替换命令:a、i、c六、awk命令awk是一个强大的文本分析工具(严格来说是一种编程语言),相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。它之所以叫AWK是因为其取了三位创始人$AlfredAho$,$PeterWeinberger$,和$BrianKernighan$的FamilyName的首字符。1、awk的基本语法格式awk常用选项参数:选项含义-Ffs指定以fs作为输入行的分隔符,awk命令默认分隔符为空格或制表符。-ffile从脚本文件中读取awk脚本指令,以取代直接在命令行中输入指令。-vvar=val在执行处理过程之前,设置一个变量var,并给其设备初始值为val。awk的COMMAND部分由两部分组成,分别为匹配规则和执行命令,如下所示:这里的匹配规则,和sed命令中的COMMAND部分作用相同,用来指定命令可以作用到文本内容中的具体行,可以使用字符串或者正则表达式(比如/root/,表示查看含有root字符串的行)指定。另外需要注意的是,整个COMMAND部分必须是用单引号('')括起,而其中的执行命令部分需要用大括号({})括起来。在awk程序执行时,如果没有指定执行命令,则默认会把匹配的行输出;如果不指定匹配规则,则默认匹配文本中所有的行。2、awk使用位置参数变量awk的主要特性之一是其处理文本文件中数据的能力,它会默认自动把一行数据按空白字符(例如空格或制表符)分隔,并给每段数据一个位置参数($1、$2.....)。默认情况下,awk会将如下变量分配给它在文本行中发现的数据字段:$0代表整个文本行;$1代表文本行中的第1个数据字段;$2代表文本行中的第2个数据字段;$n代表文本行中的第n个数据字段。前面说过,在awk中,默认的字段分隔符是任意的空白字符(例如空格或制表符)。在文本行中,每个数据字段都是通过字段分隔符划分的。awk在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。这是使用默认的空白字符作为分隔符,我们还可以使用-F选项参数指定分隔符,比如下图以.(点)作为分隔符:3、awkCOMMAND中使用多个命令awk允许将多条命令组合成一个正常的程序。要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号(;)隔开即可,例如:4、awkBEGIN关键字默认情况下,awk会从输入中读取一行文本,然后针对该行的数据执行程序命令,但有时可能需要在处理数据前运行一些程序命令,这就需要使用BEGIN关键字。BEGIN会强制awk在读取文本之前执行BEGIN脚本块的命令,例如:注意!BEGIN命令和普通命令应该写在同一个单引号('')内。5、awkEND关键字END关键字和BEGIN关键字的作用相反,它允许awk在读取完数据执行执行他们,例如:了解BEGIN和END后,我们可以大致总结出awk的工作流程:先执行BEGIN,然后读取文件,读入有/n换行符分割的一条记录,然后将记录按指定的域分隔符划分域,填充域,$0则表示所有域,$1表示第一个域,$n表示第n个域,随后开始执行模式所对应的动作action。接着开始读入第二条记录······直到所有的记录都读完,最后执行END操作。6、awk的变量awk的变量分为内置变量和自定义变量;内置变量就是awk预定义好的、内置在awk内部的变量,而自定义变量就是用户定义的变量。(1)awk内置变量FS(FieldSeparator):输入字段分隔符,和-F参数作用类似,默认为空白字符OFS(OutofFieldSeparator):输出字段分隔符,默认为空白字符RS(RecordSeparator):输入记录分隔符(输入换行符),指定输入时的换行符,默认是换行符ORS(OutputRecordSeparate):输出记录分隔符(输出换行符),输出时用指定符号代替换行符,默认是换行符NF(NumberforField):当前行的字段的个数(即当前行被分割成了几列)NR(NumberofRecord):行号,输入记录的总的行号。FNR:awk可以同时处理多个文件,因此awk提供此变量用于获取各文件分别计数的行号ARGC:命令行参数的个数ARGV:数组,保存的是命令行所给定的各参数FILENAME:awk浏览的文件名此外,$0变量是指整条记录。$1表示当前行的第一个字段,$2表示当前行的第二个字段,......以此类推。统计/etc/passwd:文件名,每行的行号,每行的列数,对应的完整行内容:在命令中,我们首先使用内置命令FS定义了行分隔符为分号,之后awk开始逐行处理文件,使用内置变量获得文件名(FILENAME)、行号(NR)、每行的列数(NF)以及整行的内容($0),最后完成打印动作。(2)awk自定义变量在awk中自定义变量有两种方式使用-v参数在COMMAND外部定义一般格式:自定义的变量在awk的COMMAND中可以直接使用,不需要向shell脚本那样在变量名前加上$同时使用-v参数也可以接受shell变量的值直接在COMMAND内部定义并使用统计/etc/passwd的账户人数变量默认初始化为0,但是当初始值不为0的时候可以使用=赋值7、awk的格式化输出awk中同时提供了print和printf两种打印输出的函数。其中print函数的参数可以是变量、数值或者字符串。字符串必须用双引号引用,参数用逗号分隔。如果没有逗号,参数就串联在一起而无法区分。这里,逗号的作用与输出文件的分隔符的作用是一样的,只是后者是空格而已。printf函数,其用法和c语言中printf基本相似,可以格式化字符串,输出复杂时,printf更加好用,代码更易懂。统计磁盘可用空间大于10000字节的分区(1)使用print函数print函数会自动输出一个换行,一般我们都需要指定输出的格式(2)使用printf函数printf函数的使用方式和C语言的printf函数类似,使用的时候参考C语言的语法格式就可以了8、awk条件语句awk中的条件语句是从C语言中借鉴来的,关键字和使用方法参考C语言就可以了,下面给出一些用法示例:统计某个文件夹下的文件占用的字节数,过滤4096大小的文件(一般都是文件夹):9、awk循环语句awk中的循环语句同样借鉴于C语言,支持while、do/while、for、break、continue,这些关键字的语义和C语言中的语义完全相同。用的时候直接用就好了,没啥特别的。(1)for语句(2)while语句(3)do-while语句10、awk的数组因为awk中数组的下标可以是数字和字母,数组的下标通常被称为关键字(key)。值和关键字都存储在内部的一张针对key/value应用hash的表格里。由于hash不是顺序存储,因此在显示数组内容时会发现,它们并不是按照你预料的顺序显示出来的。数组和变量一样,都是在使用时自动创建的,awk也同样会自动判断其存储的是数字还是字符串。一般而言,awk中的数组用来从记录中收集信息,可以用于计算总和、统计单词以及跟踪模板被匹配的次数等等。数组使用的语法格式:AWK可以使用关联数组这种数据结构,索引可以是数字或字符串。AWK关联数组也不需要提前声明其大小,因为它在运行时可以自动的增大或减小。显示/etc/passwd的账户11、awk的正则匹配awk还支持正则匹配,正则表达式的语法规则参考上面grep命令的正则语法(通用的),不过需要注意的是:awk的匹配语句要写在//内部才可以被awk识别。示例给定一个包含电话号码列表(一行一个电话号码)的文本文件file.txt,写一个bash脚本输出所有有效的电话号码。你可以假设一个有效的电话号码必须满足以下两种格式:(xxx)xxx-xxxx或 xxx-xxx-xxxx。(x表示一个数字)这个例子可以使用正则表达式配合grep-P、sed和awk,这里使用awk来实现awk编程的还有很多内容,这里只罗列简单常用的用法,更多请参考http://www.gnu.org/software/gawk/manual/gawk.html

    LoveIT 2020-05-21
    Linux
  • Shell教程—Linux管道详解

    Shell教程—Linux管道详解

    Shell除了有输入/输出重定向的功能,还可以将两个或者多个命令(程序或者进程)连接到一起,把一个命令的输出作为下一个命令的输入,以这种方式连接的两个或者多个命令就形成了一种特殊的文件称为管道(pipe)。Linux中管道分为两种:匿名管道和命名管道一、匿名管道我们常见的管道符|它是一个匿名管道,只能用于具有亲缘关系的进程之间,这是它与命名管道的最大区别。命名管道叫namedpipe或者FIFO(先进先出),可以用命令mkfifo创建。1、匿名管道的命令语法格式Linux匿名管道的具体语法格式如下:当在两个命令之间设置管道时,管道符|左边命令的输出就变成了右边命令的输入。只要第一个命令向标准输出写入,而第二个命令是从标准输入读取,那么这两个命令就可以形成一个管道。大部分的Linux命令都可以用来形成管道。2、示例:(1)寻找mysql进程(2)获取docker中所有的容器(3)获取当前目录下文件的个数等等,管道还有好多用处,这里仅仅给出一些示例。二、命名管道namedpiped我们常用的|是一个匿名管道,它只能用于具有亲缘关系的进程之间,当需要在爱多个线程之间使用管道的时候就需要命名管道。1、命名管道的创建命名管道的语法格式如下:2、示例:使用mkfifo命令就可以创建一个有名字的管道,前文提到过,管道实际也是一种文件,只不过它具有先进先出以及管道中的数据被读取以后就消失的特性(类似于队列)。(1)使用命令管道实现多终端交互在/dev/pts/0终端上创建了一个名为/tmp/fifo3的管道,并向管道总中输入数据,在/dev/pts/1终端获取数据在/dev/pts/0终端创建管道,并向管道输入数据在/dev/pts/1终端通过管道接收到数据值的注意的是,在/dev/pts/1终端终端接收数据的时候,当/dev/pts/0没有给管道输入数据的时候,/dev/pts/1终端会一直等待,当数据从管道读取之后再次尝试读取发现之前的数据没有了。这也进一步验证了管道的两个特性:FIFO和数据被读取以后就消失。(2)使用命名管道和FD实现对Shell并发的控制创建一个多线程的网络检查脚本,要求最多开启5个线程同时工作执行结果【部分】:三、管道的实现机制管道技术是Linux操作系统中由来已久的一种进程间通信机制。所有的管道技术,无论是半双工的匿名管道,还是命名管道,它们都是利用FIFO排队模型来指挥进程间的通信。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:限制管道的大小。实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。不过在Linux中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个file结构指向同一个临时的VFS索引节点,而这个VFS索引节点又指向一个物理页面而实现的。

    LoveIT 2020-05-17
    Linux
  • Shell教程—Shell输入/输出重定向

    Shell教程—Shell输入/输出重定向

    LinuxShell重定向分为两种,一种输入重定向,一种是输出重定向;从字面上理解,输入输出重定向就是改变输入与输出的方向的意思。一般情况下,我们以CPU为参考,CPU读入数据就是输入,CPU写出数据就是输出。在Linux中从键盘输入数据被称为标准输入,向屏幕/显示器上显示数据被称为标准输出。一、硬件设备和文件描述符1、文件描述符定义计算机的硬件设备有很多,常见的输入设备有键盘、鼠标、麦克风、手写板等,输出设备有显示器、投影仪、打印机等。不过,在Linux中,标准输入设备指的是键盘,标准输出设备指的是显示器。Linux中一切皆文件,包括标准输入设备(键盘)和标准输出设备(显示器)在内的所有计算机硬件都是文件。为了表示和区分已经打开的文件,Linux会给每个文件分配一个ID,这个ID就是一个整数,被称为文件描述符(FileDescriptor)。文件描述符文件名类型0stdin标准输入文件1stdout标准输出文件2stderr标准错误输出文件3以上filiename其他文件Linux程序在执行任何形式的I/O操作时,都是在读取或者写入一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数,它的背后可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接。stdin、stdout、stderr默认都是打开的,在重定向的过程中,0、1、2这三个文件描述符可以直接使用。2、查看一个进程打开了哪些文件语法:ll/proc/[进程ID]/fd/proc/[进程ID]/fd这个目录专门用于存放文件描述符。0、1、2也就是宏STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO。二、LinuxShell输出重定向通过上面的学习我们了解到Linux标准输出是显示器,输出重定向就是将指令命令的结果不在输出到显示器上,而是输出到其它地方,一般是指定文件中。这样做的最大好处就是把命令的结果保存起来,当我们需要的时候可以随时查询。Bash支持的输出重定向符号如下表所示:类型符号作用标准输出重定向command>file以覆盖的方式,把命令command的正确执行结果输出到文件file中command>>file>以追加的方式,把命令command的正确执行结果输出到文件file中标准错误输出重定向command2>file以覆盖的方式,把命令command的错误执行结果输出到文件file中command2>>file以追加的方式,把命令command的错误执行结果输出到文件file中正确输出和错误信息同时保存command>file2>&1以覆盖的方式,把正确输出和错误信息同时保存到同一个文件(file)中command>>file2>&1以追加的方式,把正确输出和错误信息同时保存到同一个文件(file)中command>file12>file2以覆盖的方式,把正确的输出结果输出到file1文件中,把错误信息输出到file2文件中。command>>file12>>file2以追加的方式,把正确的输出结果输出到file1文件中,把错误信息输出到file2文件中。command>file2>file【不推荐】这两种写法会导致file被打开两次,引起资源竞争,所以stdout和stderr会互相覆盖command>>file2>>file>注意在文件重定向中我们使用了两个关键点的符号:>和>>,其中前者表示覆盖原来的文本,后者表示在原来的文本后面追加。其实文重定向的标准写法是:fd>file或者fd>>file,其中fd表示文件描述符,不写默认为1,也就表示标准输出。当文件描述符为1时,一般都省略不写,如上表所示;当然,如果你愿意,也可以将command>file写作command1>file,但这样做是多此一举。当文件描述符为大于1的值时,比如2,就必须写上。需要重点说明的是,fd和>之间不能有空格,否则Shell会解析失败;>和file之间的空格可有可无。为了保持一致,我习惯在>两边都不加空格。案例(1)使用echo命令将Shell脚本的运行结果输出到指定的文件中写一个脚本,检测当前系统对CPU的使用率,把脚本加入到crontab定时任务中,每分钟执行一次,并把执行结果输出到/home/cpu_monitor.txt文本中执行结果:向文件中输出命令执行的错误信息同样可以参考上面的方法,不同的地方是需要在>或>>前面加上2,注意不要有空格。(2)/dev/null文件在Unix系统中,/dev/null,或称空设备,是一个特殊的设备文件,它丢弃一切写入其中的数据(但报告写入操作成功),读取它则会立即得到一个EOF。在程序员行话,尤其是Unix行话中,/dev/null被称为位桶(bitbucket)或者黑洞(blackhole)。空设备通常被用于丢弃不需要的输出流,或作为用于输入流的空文件。当你读它的时候,它会提供无限的空字符(NULL,ASCIINUL,0x00)。因此利用这个文件,如果你既不想把命令的输出结果保存到文件,也不想把命令的输出结果显示到屏幕上,干扰命令的执行,那么可以把命令的所有结果重定向到/dev/null文件中。如下所示:关于/dev/null的用处还用很多,这里只是举一个例子,在使用的时候大家可以把/dev/null当成Linux系统的垃圾箱,任何放入垃圾箱的数据都会被丢弃,不能恢复。三、LinuxShell输入重定向输入重定向就是改变输入的方向,不再使用键盘作为命令输入的来源,而是使用文件作为命令的输入。符号说明command<file将file文件中的内容作为command的输入。command<<END从标准输入(键盘)中读取数据,直到遇见分界符END才停止(分界符可以是任意的字符串,用户自己定义)。command<file1>file2将file1作为command的输入,并将command的处理结果输出到file2。和输出重定向类似,输入重定向的完整写法是fd<file,其中fd表示文件描述符,如果不写,默认为0,也就是标准输入文件。案例(1)统计给定文本中有多少行统计刚才CPU监控脚本定时计划向/home/cpu_monitor.txt文本中输出的文本的行数(2)逐行读取文件读取/etc/scripts/numbers.txt文件(每个数字占一行),读取到脚本中对文件中的数字排序执行结果:(3)HereDocumentHereDocument是Shell中的一种特殊的重定向方式,用来将输入重定向到一个交互式Shell脚本或程序。它的基本的形式如下:它的作用是将两个delimiter之间的内容(document)作为输入传递给command。

    LoveIT 2020-05-17
    Linux
  • Shell教程—Shell函数

    Shell教程—Shell函数

    和大多数编程语言一样,Shell也可以定义和使用函数(function)。Shell函数的本质是一段可以重复使用的脚本代码,这段代码被提前编写好了,放在了指定的位置,使用时直接调取即可。Shell函数定义的语法格式如下:说明:Shell的函数定义的时候可以使用function关键字,也可以不使用funname:函数的名字在每个语句后面可以写上;(分号)也可以不写Shell的函数没有形参的概念,当需要给函数传参可以使用位置参数进行传参returnint:参数返回,可以显示加:return返回,如果不加,将以最后一条命令运行结果,作为返回值。return后跟数值n(0-255)函数的返回值使用$?获取函数定义的简化写法函数定义的时候可以不写function关键字如果写了function关键字,可以不写函数名后面的括号(注意这种情况下函数名和{之间要有空格):具体使用哪种方式定义函数,执行的效果没有区别,完全取决于个人喜好!函数的参数Shell中的函数在定义时不能指明参数,但是在调用时却可以传递参数。函数参数是Shell位置参数的一种,在函数内部可以使用$n来接收,例如,$1表示第一个参数,​$2表示第二个参数,依次类推。除了$n,还有另外三个比较重要的变量:$#可以获取传递的参数的个数;$@或者$*可以一次性获取所有的参数。示例:执行结果:函数的调用Shell不限制定义和调用的顺序,你可以将定义放在调用的前面,也可以反过来,将定义放在调用的后面。1、无参函数的调用调用无参函数,直接在需要的地方写函数名就行了,不需要写括号2、有参函数的调用如果传递参数,那么多个参数之间以空格分隔,函数名字后面都需要带括号:案例在案例中将着重演示使用Shell如何实现递归函数的定义和使用1、定义一个函数计算n(1<=n<=20)的阶乘执行结果:通过这个程序,我们需要了解Shell中递归函数的编写手段以及当函数的返回值超过255了之后我们如何处理。2、二分查找写一个脚本实现二分查找执行结果:

    LoveIT 2020-05-16
    Linux
  • Shell教程—Shell数组

    Shell教程—Shell数组

    数组中可以存放多个值。BashShell只支持一维数组(不支持多维数组),初始化时不需要定义数组大小。Shell中的数组有两类:普通数组和关联数组。普通数组就是我们熟悉的一维数组,它的索引只能为整数;关联数组实质是一种key-value的集合,key和value既可以是整数也可以是字符串。一、Shell数组的基本语法1、普通数组(1)定义数组Shell中没有多维数组的概念,只有一维数组,它的语法格式如下:(2)查看数组(3)访问数组中的元素读取数组元素值的一般格式是:2、关联数组(1)定义数组关联数组可以理解为一种key-value集合,它的语法格式如下:(2)查看数组(3)访问数组元素读取关联不能用整型下标值,而应该使用在定义时给定的key,一般格式是:二、案例1、将/etc/hosts每行host读取存入hosts数组(简单)执行结果:2、编写一个脚本对给定文件中的数字进行排序,并输出在numbers.txt文本中有大量(大约10w个)随机生成的数字,现在要求对这些数字进行排序实现排序:执行结果:3、统计/etc/passwd文件中不同Shell的个数/etc/passwd文件中定义了许多Shell,现在需要统计不同Shell的个数执行结果:

    LoveIT 2020-05-14
    Linux
  • Shell教程—流程控制之循环语句for、while、until

    Shell教程—流程控制之循环语句for、while、until

    BashShell中主要提供了三种循环方式:for、while和until一、for循环与其他编程语言类似,Shell支持for循环。for循环的运作方式,是将串行的元素意义取出,依序放入指定的变量中,然后重复执行含括的命令区域(在do和done之间),直到所有元素取尽为止。其中,串行是一些字符串的组合,彼此用$IFS所定义的分隔符(如空格符)隔开,这些字符串称为字段。for循环一般格式为:案例1:Shell打印执行目录的目录树tree自己写一个脚本,实现和tree命令一样可以将自动目录打印成可视化的文件数的功能。执行结果【部分】:二、while循环在案例一种我们在打印文件目录树中也是用了while循环。while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件。while循环的语法:案例二:使用while实现从1加到n,n由用户输入,并且输出最终的和执行结果:无限循环的语法格式:(1)写法一:使用一个:作为条件,就可以实现无限循环(2)写法二:使用true关键字(3)写法三:使用for语句的C风格三、until循环until循环执行一系列命令直至条件为true时停止。until循环与while循环在处理方式上刚好相反。一般while循环优于until循环,但在某些时候—也只是极少数情况下,until循环更加有用。until语法格式:案例三:使用until打印九九乘法表执行结果:四、跳出循环在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break和continue。1、break命令break关键字的语法:breaknn表示跳出循环的层数,如果省略n,则表示跳出当前的整个循环。break关键字通常和if语句一起使用,即满足条件时便跳出循环。案例四:Shell脚本和用户交互执行结果:案例五:使用break跳出2层循环执行结果:2、continue命令continue关键字的语法为:n表示循环的层数:如果省略n,则表示continue只对当前层次的循环语句有效,遇到continue会跳过本次循环,忽略本次循环的剩余代码,直接进入下一次循环。如果带上n,比如n的值为2,那么continue对内层和外层循环语句都有效,不但内层会跳过本次循环,外层也会跳过本次循环,其效果相当于内层循环和外层循环同时执行了不带n的continue。这么说可能有点难以理解,稍后我们通过代码来演示。执行结果:从运行结果可以看出,遇到continue2时,不但跳过了内层for循环,也跳过了外层for循环。break和continue的区别break用来结束所有循环,循环语句不再有执行的机会;continue用来结束本次循环,直接跳到下一次循环,如果循环条件成立,还会继续循环。

    LoveIT 2020-05-13
    Linux
  • Shell教程—模式匹配case

    Shell教程—模式匹配case

    case语句和if...elif...else语句一样都是多分支条件语句,不过和多分支if条件语句不同的是,case语句只能判断一种条件关系,而if语句可以判断多种条件关系。case语句的语法结构:case语句应该注意一下几点:case语句会取出变量中的值,然后与语句体中的值逐一比较。如果数值符合,则执行对应的程,之后就不会在往下走;如果数值不符,则依次比较下一个值;如果所有的值都不符合,则执行*)(*代表所有其他值)中的程序。case语句以case开头,以esac结尾。在每个分支程序之后要以;;(双分号)结尾,代表该程序段结束(千万不要忘记)。案例1:模式匹配用户输入选择在我们的脚本中经常有让用户输入Y/N表示是否确定执行接下来的操作,这个用if-else也可以做,但是当考虑到多个判断条件的时候if写出来的语句就显得过于繁杂,用case语句就可以简化案例二:实现一个简单的系统管理工具执行脚本,首先打印支持的工具和代号,然后用户输入工具的代号执行功能。

    LoveIT 2020-05-12
    Linux
  • Linux Vi/Vim常用操作

    Linux Vi/Vim常用操作

    所有的UnixLike系统都会内建vi文书编辑器,其他的文书编辑器则不一定会存在。但是目前我们使用比较多的是vim编辑器。vim具有程序编辑的能力,可以主动的以字体颜色辨别语法的正确性,方便程序设计。vi/vim共分为三种模式,分别是命令模式(Commandmode),输入模式(Insertmode)和底线命令模式(Lastlinemode)。三种模式的切换可以用下图表示:1、命令模式下常用操作在shell终端下当我们输入vimxxx(xxx指的是文件名)就可进入到vim的编辑界面,此时vim默认处于命令模式中,在此模式下可以进行的光标移动、复制粘贴、搜索替换等操作移动光标的方法移动光标的方法功能示例h或左箭头键(←)光标向左移动一个字符20h或20←光标向左移动20个字符j或下箭头键(↓)光标向下移动一行15j或15↓光标向下移动15行k或上箭头键(↑)光标向上移动一行同理l或右箭头键(→)光标向右移动一个字符同理0或功能键[Home]将光标移动到此行的第一个字符$或功能键[End]将光标移动到此行的最后一个字符G移动到这个文档的最后一行nGn为数字。移动到这个文档的第n行。20G移动到这个文档的第20行gg相当于1G,移动到此文档的第一行n<Enter>n为数字。光标向下移动n行5<Enter>光标向下移动5行搜索替换的方法搜索替换/word向光标之下寻找一个名称为word的字符串。例如要在文档内搜寻abc这个字符串,就输入/abc即可?word向光标之上寻找一个字符串名称为word的字符串。nn是英文按键。代表重复前一个搜寻的动作。举例来说,如果刚刚我们执行/abc去向下搜寻abc这个字符串,则按下n后,会向下继续搜寻下一个名称为abc的字符串。如果是执行?abc的话,那么按下n则会向上继续搜寻名称为abc的字符串!NN是英文按键。与n刚好相反,它是向上寻找:n1,n2s/word1/word2/gn1与n2为数字。在第n1与n2行之间寻找word1这个字符串,并将该字符串取代为word2复制、粘贴、删除的方法删除、复制与粘贴x,X在一行字当中,x为向后删除一个字符(相当于[del]按键),X为向前删除一个字符(相当于[backspace]亦即是退格键)dd删除光标所在的那一行nddn数字,表示删除光标所在行一下的n行,例如20dd则是删除20行(常用)d^或d1G删除光标所在到第一行的所有数据dG全文删除yy复制光标所在的那一行数据nyyn为数字。复制光标所在的向下n行,例如20yy则是复制20行y^或y1G复制光标所在行到第一行的所有数据yG复制光标所在行到最后一行的所有数据ggyG全文复制p将已复制的数据在光标下一行贴上P将已复制的数据在光标上一行贴上u复原前一个动作(撤销)。【Ctrl】+r或.重做上一个动作2、编辑模式下常用操作在命令模式下我们按以下几个特定的字母就会进入到不同的编辑模式中,在编辑模式中我们就可以编辑文档。进入输入或取代的编辑模式i,I进入输入模式(Insertmode):i为从目前光标所在处输入,I为在目前所在行的第一个非空格符处开始输入。a,A进入输入模式(Insertmode):a为从目前光标所在的下一个字符处开始输入,A为从光标所在行的最后一个字符处开始输入。o,O进入输入模式(Insertmode):这是英文字母o的大小写。o为在目前光标所在的下一行处输入新的一行;O为在目前光标所在处的上一行输入新的一行!r,R进入取代模式(Replacemode):r只会取代光标所在的那一个字符一次;R会一直取代光标所在的文字,直到按下ESC为止;Esc按键退出编辑模式,回到命令模式3、底线模式下常用操作指令行的储存、离开等指令:w将编辑的数据写入硬盘档案中:w!若文件属性为只读时,强制写入该档案。不过,到底能不能写入,还是跟你对该档案的档案权限有关:q退出编辑器:q!若曾修改过文档,又不想保存,使用!为强制退出不保存。:wq保存并退出,若为:wq!则为强制保存并退出:setnu显示行号,设定之后,会在每一行的前缀显示该行的行号:setnonu取消显示行号

    LoveIT 2020-05-11
    Linux
  • Shell教程—流程控制之if

    Shell教程—流程控制之if

    在我们常见的高级语言中都有if这个关键字,在shell中也有这个关键字,它也是用来做条件判断的。shell中的if的语法格式如下:一、if语句的基本语法1、if相当于C语言中的if语句最后那个fi必须要写,这也是他语法的一部分。2、ifelse相当于C语言中的if-else语句3、ifelse-ifelse从语法上来看,shell中的if和C、Java等语言的条件判断语句还是有些许差别的,并且不光是这些差别:(1)shell中的else分支如果没有语句,那就不要写出来,这样会报错(2)if后的condition一定要是一个能返回true或false的语句,虽然我们常常将1认为是true、0认为是false,但是这里的condition运算结果只能是true或false,否则,即使执行结果是1或0,都会认为condition这个条件是具备的,就不走其他分支了接下来我们就来学习一下,if的conditon该如何写。二、Shell条件测试Shell条件测试有两个命令test和[,他们两个的作用是一样的,用于检查某个条件是否成立,可以进行数值、字符和文件三个方面的测试。[命令和test命令是两个等价的命令,他们两可以相互替换。1、数值测试参数说明INTEGER1-eqINTEGER2INTEGER1isequaltoINTEGER2(两个数都相等为真)INTEGER1-geINTEGER2INTEGER1isgreaterthanorequaltoINTEGER2(大于等于为真)INTEGER1-gtINTEGER2INTEGER1isgreaterthanINTEGER2(大于为真)INTEGER1-leINTEGER2INTEGER1islssthanorequaltoINTEGER2(小于等于为真)INTEGER1-ltINTEGER2INTEGER1islessthanINTEGER2(小于为真)INTEGER1-neINTEGER2INTEGER1isnotequaltoINTEGER2(不等于为真)示例:判断两个数大小执行结果:上面程序中[]可以使用test命令替换,感兴趣的小伙伴可以自行测试一下。2、字符串测试参数说明-nSTRING字符串的长度不为零则为真-zSTRING字符串的长度为零则为真STRING1=STRING2两个字符串相等STRING1!=STRING2两个字符串不相等示例:判断输入的两个字符串是否相等执行结果:3、文件测试参数说明-eFILE|DIR如果文件或目录存在则为真(常用)-rFILE如果文件存在且当前用户可读则为真-wFILE如果文件存在且可写则为真-xFILE如果文件存在且可执行则为真-sFILE如果文件存在且至少有一个字符则为真-dDIR如果文件存在并且是目录则为真(常用)-fFILE如果文件存在且为普通文件(常用)-cFILE如果文件存在且为字符型特殊文件-bFILE如果文件存在且为块特殊文件-LFILE文件存在并且是链接文件FILE1-efFILE2FILE1和FILE2具有相同的设备和inode编号FILE1-ntFILE2FILE1比FILE2新FILE1-otFILE2FILE1比FILE2旧示例:编写一个脚本,让用户输入一个目录的路径,脚本分别输出该目录下文件和目录的个数。执行结果:4、逻辑运算符逻辑运算符可以用于连接逻辑表达式的各个条件参数说明举例!逻辑非运算[!$fileName="conf.d"]判断文件名知否叫conf.d如果是的话返回false-a逻辑与运算,遵循逻辑短路原则[-d$file-a`uname-s`="Linux"]判断文件是否是录目录并且当前系统是Linux,如果都是,那么返回true-o逻辑或运算,遵循逻辑短路原则[$(uname-s)="Linux"-o$(uname-s)="FreeBSD"]判断当前系统如果是Linux或FreeBSD的话返回true&&逻辑或,遵循逻辑短路原则a=100b=200[[$agt50]]&&[[$alt$b]]判断a是否大于50并且小于b,如果都满足,返回true||逻辑与,遵循逻辑短路原则a=100b=200[[$agt50]]||[[$blt500]]判断a是否大于50或b小于500,如果有一个满足,返回true在Shell中if判断中多条件的写法比如比较a>b且a<c,可以的写法如下:三、if练习1、判断用户是否存在执行脚本,输入一个用户名,脚本判断此用户是否存在,如果不存在时根据用户输入的Y/N来决定是否创建该用户2、一键检测服务器上docker是否安装、启动首先脚本判断docker是否安装,如果没有安装就要执行安装;如果已安装,就判断docker是否启动,如果没有启动,就启动docker服务。代码仅供参考,如有不妥之处还望大佬指出,不胜感激!!!

    LoveIT 2020-05-11
    Linux
  • Shell教程—Shell打印输出命令

    Shell教程—Shell打印输出命令

    在Linux中有两个常见的Shell输出命令echo和printf,他们都可以打印,但是又有些许差别,接下来我们就来了解一下他们吧。一、echo命令echo是Shell的一个内部指令,用于在屏幕上打印出指定的字符串的标准输出。命令格式:echo命令的常见用于如下:(1)打印普通字符(2)显示转义字符(3)显示变量的值(4)显示命令的执行结果二、ptintf命令学过C语言的同学应该对C中的标准输出函数printf不陌生,几乎编程一入门第一个了解的是main函数,第二个可能就是printf函数了,Linux中的printf命令模仿C程序库(library)里的printf()程序。printf由POSIX标准所定义,因此使用printf的脚本比使用echo移植性好。printf使用引用文本或空格分隔的参数,外面可以在printf中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认printf不会像echo自动添加换行符,我们可以手动添加\n。printf命令的语法:参数说明:format-string:为格式控制字符串arguments:为参数列表。总体上来说,printf命令和C的printf()函数作用类似。这里对比C语言printf()函数来说明一下不同之处:(1)printf命令不用加括号(2)format-string可以没有引号,但最好加上,单引号双引号均可。实例:(3)参数多于格式控制符(%)时,format-string可以重用,可以将所有参数都转换。(4)arguments使用空格分隔,不用逗号。执行结果:剩下的printf命令还有一些格式替代符以及转移字符,这里就不详细的说了,基本和C语言中的printf函数类似。总结当我们需要简单的输出一下结果的时候使用echo就够了,很简单,也没有繁琐的参数,但是当我们需要格式化输出的时候,printf命令将是不二之选。

    LoveIT 2020-05-08
    Linux
  • Shell教程—Shell入门

    Shell教程—Shell入门

    Shell教程—Shell入门Shell是一个用C语言编写的命令解释器(commandinterpreter),是Unix操作系统的用户接口,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言。Shell是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。下图所示用户、shell和操作系统的关系:Shell脚本(shellscript),是一种为shell编写的脚本程序,shell和shellscript是两个不同的概念。Shell编程跟JavaScript、php编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。一、Shell的种类Linux的Shell种类众多,我们可以使用命令cat/etc/shells查看系统中安装的shell。通常操作系统内核(kernel)与shell是独立的套件,而且都可被替换。不同的操作系统使用不同的shell,同一个kernel之上可以使用不同的shell。常见的shell分为两大主流:shBourneShell(/usr/bin/sh或/bin/sh),Solaris,hpux默认shellBourneAgainShell(/bin/bash),Linux系统默认的shellcshCShell(/usr/bin/csh)KShell(/usr/bin/ksh)ShellforRoot(/sbin/sh)BourneAgainShell,由于易用和免费,Bash在日常工作中被广泛使用。同时,Bash也是大多数Linux系统默认的Shell。查看当前系统使用的shell—echo$SHELL二、Shell环境变量定义1、临时环境变量所谓临时变量是指在用户在当前登陆环境生效的变量,用户登陆系统后,直接在命令行上定义的环境变量便只能在当前的登陆环境中使用。当退出系统后,环境变量将不能下次登陆时继续使用。2、将环境变量永久生效通过将环境变量定义写入到配置文件中,用户每次登陆时系统自动定义,则无需再到命令行重新定义。定义环境变量的常见配置文件如下:/etc/profile针对系统所有用户生效,此文件应用于所有用户每次登陆系统时的环境变量定义$HOME_name/.bash_profile针对特定用户生效,$HOME为用户的宿主目录,当用户登陆系统后,首先继承/etc/profile文件中的定义,再应用$HOME/.bash_profile文件中的定义。3、系统预定义的环境变量系统环境变量对所有用户有效,如:PATH、HOME、SHELL、PWD等等,如下用echo命令打印上述的系统环境变量:三、Shell脚本编程基本概念1、shell脚本的格式一个shell脚本通常包含如下部分:首行第一行内容在脚本的首行左侧,表示脚本将要调用的shell解释器,比如#!/bin/bash,就表示要代调用BourneAgainShell解释器来执行shell脚本#!符号,英文名叫Shebang([ʃɪˈbæŋ]),没有中文名。该符号能够被内核识别成是一个脚本的开始,这一行必须位于脚本的首行,/bin/bash是bash程序的绝对路径,在这里表示后续的内容将通过bash程序解释执行。注释注释符号#放在需注释内容的前面。内容任何可执行的Linux命令以及shell编程的语法关键字2、赋予shell脚本的执行权限在Linux中创建的一个文件默认是没有执行权限的,包括shell脚本没有执行权限是不能被执行的,需要我们给他权限:chmod755文件名或chmod+x文件名3、shell脚本的执行(1)输入脚本的绝对路径或相对路(2)bash或sh+脚本名注:当脚本没有x权限时,root和文件所有者通过该方式可以正常执行。(3)在脚本的路径前再加"."或source上面两种执行shell脚本的方法都是在子shell中执行的,没有在当前shell中执行,当我们需要在当前shell执行shell的时候,我们就需要使用这种方法。goto-usrlocal.sh脚本的内容如下:一张图说明差别:区别:第一种和第二种会新开一个bash,不同bash中的变量无法共享。但是使用../脚本.sh这种方式是在同一个shell里面执行的。四、Shell变量        变量是shell传递数据的一种方式,用来代表每个取值的符号名。当shell脚本需要保存一些信息时,如一个文件名或是一个数字,就把它存放在一个变量中。1、变量设置规则:(1)变量名称可以由字母,数字和下划线组成,但是不能以数字开头,环境变量名建议大写,便于区分。(2)在bash中,变量的默认类型都是字符串型,如果要进行数值运算,则必须指定变量类型为数值型。(3)变量用等号连接值,等号左右两侧不能有空格。(4)变量的值如果有空格,需要使用单引号或者双引号包括。2、变量分类(1)自定义变量用户自定义的变量由字母或下划线开头,由字母,数字或下划线序列组成,并且大小写字母意义不同,变量名长度没有限制。定义变量:变量名=变量值变量名必须以字母或下划线开头,区分大小写,ip1=192.168.2.115引用变量:$变量名或${变量名}查看变量:echo$变量名或${变量名}取消变量:unset变量名作用范围:在当前shell有效示例:在定义或引用变量的时候要特别注意:(1)""和''两个引号的作用不同,双引号是弱引用,内部可以使用变量;单引号是强引用,内部无法使用变量。(2)``,反引号的作用是命令替换,作用等价于$(),写在它们之中的命令会被优先执行。(2)系统环境变量保存和系统操作环境相关的数据。HOME、HOME、PWD、SHELL、SHELL、USER等等,可以使用env命令查看当前系统定义的所用系统变量,对于这些系统环境变量在我们编写shell脚本的时候可以直接使用。定义环境变量:引用环境变量:$变量名或${变量名}查看环境变量:echo$变量名或${变量名}取消环境变量:unset变量名环境作用范围:在当前shell和子shell有效示例:系统环境变量我们熟悉的比如配置Java的环境变量,在Linux系统中比如有个jdk1.8的JDK在/usr/local/java下,我们配置系统环境变量可以这么配置:(3)位置参数变量主要用来在执行命令的时候向脚本中传递参数或数据,变量名不能自定义,变量作用固定。格式含义$nn为数字,0代表命令本身,0代表命令本身,$1-$9代表第1到第9个参数,10以上的参数需要用大括号包含,如${10}。示例:要特别注意的是,$0表示的时shell脚本本生,在使用的时候要特别注意!!!(4)预定义变量预定义变量是Bash中已经定义好的变量,变量名不能自定义,变量作用也是固定的,我们也可以直接使用这些变量。格式含义$?执行上一个命令的返回值执行成功,返回0,执行失败,返回非0(具体数字由命令决定)$$当前进程的进程号(PID),即当前脚本执行时生成的进程号$!后台运行的最后一个进程的进程号(PID),最近一个被放入后台执行的进程&$*代表命令行中所有的参数,把所有的参数看成一个整体。以"112…$n"的形式输出所有参数$@代表命令行中的所有参数,把每个参数区分对待。以"1""1""2"…"$n"的形式输出所有参数$#代表命令行中所有参数的个数。添加到shell的参数个数(5)read命令Linuxread命令用于从标准输入读取数值。read内部命令被用来从标准输入读取单行数据。这个命令可以用来读取键盘输入,当使用重定向的时候,可以读取文件中的一行数据。语法:参数说明:-a后跟一个变量,该变量会被认为是个数组,然后给其赋值,默认是以空格为分割符。-d后面跟一个标志符,其实只有其后的第一个字符有用,作为结束的标志。-p后面跟提示信息,即在输入前打印提示信息。-e在输入的时候可以使用命令补全功能。-n后跟一个数字,定义输入文本的长度,很实用。-r屏蔽\,如果没有该选项,则\作为一个转义字符,有的话\就是个正常的字符了。-s安静模式,在输入字符时不再屏幕上显示,例如login时输入密码。-t后面跟秒数,定义输入字符的等待时间。-u后面跟fd,从文件描述符中读入,该文件描述符可以是exec新开启的。示例:脚本的功能很简单,就是先让用户输入是否据需执行脚本,如果输入的Y/y就据需执行,否则直接退出。当输入Y/y之后在让用户输入两个数字,之后输出两个数的和。执行结果:五、Shell数学计算1、整数计算(1)使用expr命令进行计算expr支持的操作符有:|、&、<、<=、=、!=、>=、>、+、-、*、/、%;expr支持的操作符中所在使用时需用\进行转义的操作符有:|、&、<、<=、>=、>、*;只支持整数运算。另外,expr的操作数和操作符之间要有一个空格,并且返回的结果需要用``或者$()来获取。执行结果:(2)使用$(())处理使用$(())可以直接输出结果,并且在引用变量的时候可以不用加$符号,对于有些特殊的运算符也不需要转义。同样的,它只支持整型运算。执行结果:(3)使用$[]运算$[]将中括号内的表达式作为数学运算先计算结果再输出;对​$[]中的变量进行访问时前面需要加​$;$[]支持的运算符与let相同,但也只支持整数运算。执行结果:(4)使用let命令运算let命令是这几个命令中我认为最好用的一个,因为他操作住够简单,而且支持几乎所有的运算符!!!包括括号优先、++、--等;参数可以不需要$,就可取到值进行运算;支持方幂运算let"a=3**2";但依然只支持整数运算。执行结果:小试牛刀写一个脚本监控当前系统的内存暂用百分比%。Linux系统中查看系统内存使用情况的命令是free,但是free打印出来的的信息有两行Mem和Swap,Mem就是系统内存的使用详情,于是我们需要使用grep命令只取Mem这一行的数据,之后在使用awk分别获取Mem一行中第2和3个数据(总的内存、已经使用的内存)把他们相除即可。执行结果:2、浮点数计算(1)bc命令行计算器,可以进行整数运算和浮点数运算使用bc可以通过$scale$指定精度,但是只有在除法的时候才有生效,其他运算都是安装原来有几位就输出几位;bc支持除了位操作的所有运算。执行结果:(2)使用awk处理Awk是Linux中一个处理文本文件的语言,是一个非常好用的文本分析工具,之所以叫AWK是因为其取了三位创始人$AlfredAho,PeterWeinberger,$和$BrianKernighan$的$FamilyName$的首字符。关于Awk详细的请参考我的另一篇博客Linux教程—awk命令​或者自行上网查找资料。执行结果:awk的变量跟shell的变量是独立的,所以需要使用-v进行变量传递;支持输出格式化,根据实际需要进行格式化输出;awk内置有非常多的函数,比如log、sqr、cos、sin等等。

    LoveIT 2020-05-06
    Linux