博客 (11)

因需要将服务商账户改为月结账户,在抖店“电子面单”页面无法修改已申请的服务商,只能取消合作,再重新开通,这会导致易打单无法打印面单,提示“请设置抖音电子面单地址”。

解决方法是进入易打单,在“批量打印”页面点击“设置模板”,抖店电子面单项刷新并重新勾选,保存。

xoyozo 9 个月前
699

版本:v2.4.1 (2019-11-15) 

    本文目标

    30分钟内让你明白正则表达式是什么,并对它有一些基本的了解,让你可以在自己的程序或网页里使用它。

    如何使用本教程

    别被下面那些复杂的表达式吓倒,只要跟着我一步一步来,你会发现正则表达式其实并没有想像中的那么困难。当然,如果你看完了这篇教程之后,发现自己明白了很多,却又几乎什么都记不得,那也是很正常的——我认为,没接触过正则表达式的人在看完这篇教程后,能把提到过的语法记住80%以上的可能性为零。这里只是让你明白基本的原理,以后你还需要多练习,多使用,才能熟练掌握正则表达式。

    除了作为入门教程之外,本文还试图成为可以在日常工作中使用的正则表达式语法参考手册。就作者本人的经历来说,这个目标还是完成得不错的——你看,我自己也没能把所有的东西记下来,不是吗?

    清除格式 文本格式约定:专业术语 元字符/语法格式 正则表达式 正则表达式中的一部分(用于分析) 对其进行匹配的源字符串 对正则表达式或其中一部分的说明

    隐藏边注 本文右边有一些注释,主要是用来提供一些相关信息,或者给没有程序员背景的读者解释一些基本概念,通常可以忽略。

    本文介绍的大部分正则语法,在不同的正则表达式引擎中都可以使用,但也有一些会有所差异。本文介绍的是 .Net 下的正则表达式,其它环境下的具体情况可以在读完本文后去参考官方文档,或者查看正则表达式引擎特性对比。

    最重要的是——请给我30分钟,如果你没有使用正则表达式的经验,请不要试图在30内入门——除非你是超人                :)

    正则表达式到底是什么东西?

    在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。

    很可能你使用过Windows/Dos下用于文件查找的通配符(wildcard),也就是*?。如果你想查找某个目录下的所有的Word文档的话,你会搜索*.doc。在这里,*会被解释成任意的字符串。和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比起通配符,它能更精确地描述你的需求——当然,代价就是更复杂——比如你可以编写一个正则表达式,用来查找所有以0开头,后面跟着2-3个数字,然后是一个连字号“-”,最后是7或8位数字的字符串(像010-123456780376-7654321)。

    字符是计算机软件处理文字时最基本的单位,可能是字母,数字,标点符号,空格,换行符,汉字等等。字符串是0个或更多个字符的序列。文本也就是文字,字符串。说某个字符串匹配某个正则表达式,通常是指这个字符串里有一部分(或几部分分别)能满足表达式给出的条件。

    入门

    学习正则表达式的最好方法是从例子开始,理解例子之后再自己对例子进行修改,实验。下面给出了不少简单的例子,并对它们作了详细的说明。

    假设你在一篇英文小说里查找hi,你可以使用正则表达式hi

    这几乎是最简单的正则表达式了,它可以精确匹配这样的字符串:由两个字符组成,前一个字符是h,后一个是i。通常,处理正则表达式的工具会提供一个忽略大小写的选项,如果选中了这个选项,它可以匹配hi,HI,Hi,hI这四种情况中的任意一种。

    不幸的是,很多单词里包含hi这两个连续的字符,比如him,history,high等等。用hi来查找的话,这里边的hi也会被找出来。如果要精确地查找hi这个单词的话,我们应该使用\bhi\b

    \b是正则表达式规定的一个特殊代码(好吧,某些人叫它元字符,metacharacter),代表着单词的开头或结尾,也就是单词的分界处。虽然通常英文的单词是由空格,标点符号或者换行来分隔的,但是\b并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置

    如果需要更精确的说法,\b匹配这样的位置:它的前一个字符和后一个字符不全是(一个是,一个不是或不存在)\w

    假如你要找的是hi后面不远处跟着一个Lucy,你应该用\bhi\b.*\bLucy\b

    这里,.是另一个元字符,匹配除了换行符以外的任意字符*同样是元字符,不过它代表的不是字符,也不是位置,而是数量——它指定*前边的内容可以连续重复使用任意次以使整个表达式得到匹配。因此,.*连在一起就意味着任意数量的不包含换行的字符。现在\bhi\b.*\bLucy\b的意思就很明显了:先是一个单词hi,然后是任意个任意字符(但不能是换行),最后是Lucy这个单词

    换行符就是'\n',ASCII编码为10(十六进制0x0A)的字符。

    如果同时使用其它元字符,我们就能构造出功能更强大的正则表达式。比如下面这个例子:

    0\d\d-\d\d\d\d\d\d\d\d匹配这样的字符串:以0开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字(也就是中国的电话号码。当然,这个例子只能匹配区号为3位的情形)。

    这里的\d是个新的元字符,匹配一位数字(0,或1,或2,或……)-不是元字符,只匹配它本身——连字符(或者减号,或者中横线,或者随你怎么称呼它)。

    为了避免那么多烦人的重复,我们也可以这样写这个表达式:0\d{2}-\d{8}。这里\d后面的{2}({8})的意思是前面\d必须连续重复匹配2次(8次)

    测试正则表达式

    如果你不觉得正则表达式很难读写的话,要么你是一个天才,要么,你不是地球人。正则表达式的语法很令人头疼,即使对经常使用它的人来说也是如此。由于难于读写,容易出错,所以找一种工具对正则表达式进行测试是很有必要的。

    不同的环境下正则表达式的一些细节是不相同的,本教程介绍的是微软 .Net Framework 4.x 下正则表达式的行为,所以,我向你推荐我编写的.Net下的工具 Regester。请参考该页面的说明来安装和运行该软件。

    下面是Regester运行时的截图:

    正则表达式测试器运行截图

    你也可以试试这个在线测试工具:Wegester, JavaScript正则表达式测试器。

    元字符

    现在你已经知道几个很有用的元字符了,如\b,.,*,还有\d.正则表达式里还有更多的元字符,比如\s匹配任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等\w匹配字母或数字或下划线或汉字等

    对中文/汉字的特殊处理是由.Net提供的正则表达式引擎支持的,其它环境下的具体情况请查看相关文档。

    下面来看看更多的例子:

    \ba\w*\b匹配以字母a开头的单词——先是某个单词开始处(\b),然后是字母a,然后是任意数量的字母或数字(\w*),最后是单词结束处(\b)

    \d+匹配1个或更多连续的数字。这里的+是和*类似的元字符,不同的是*匹配重复任意次(可能是0次),而+则匹配重复1次或更多次

    \b\w{6}\b 匹配刚好6个字符的单词

    好吧,现在我们说说正则表达式里的单词是什么意思吧:就是不少于一个的连续的\w。不错,这与学习英文时要背的成千上万个同名的东西的确关系不大                :)

    表1.常用的元字符
    代码说明
    .匹配除换行符以外的任意字符
    \w匹配字母或数字或下划线或汉字
    \s匹配任意的空白符
    \d匹配数字
    \b匹配单词的开始或结束
    ^匹配字符串的开始
    $匹配字符串的结束

    元字符^(和数字6在同一个键位上的符号)和$都匹配一个位置,这和\b有点类似。^匹配你要用来查找的字符串的开头,$匹配结尾。这两个代码在验证输入的内容时非常有用,比如一个网站如果要求你填写的QQ号必须为5位到12位数字时,可以使用:^\d{5,12}$

    这里的{5,12}和前面介绍过的{2}是类似的,只不过{2}匹配只能不多不少重复2次{5,12}则是重复的次数不能少于5次,不能多于12次,否则都不匹配。

    因为使用了^$,所以输入的整个字符串都要用来和\d{5,12}来匹配,也就是说整个输入必须是5到12个数字,因此如果输入的QQ号能匹配这个正则表达式的话,那就符合要求了。

    和忽略大小写的选项类似,有些正则表达式处理工具还有一个处理多行的选项。如果选中了这个选项,^$的意义就变成了匹配行的开始处和结束处

    正则表达式引擎通常会提供一个“测试指定的字符串是否匹配一个正则表达式”的方法,如JavaScript里的RegExp.test()方法或.NET里的Regex.IsMatch()方法。这里的匹配是指是字符串里有没有符合表达式规则的部分。如果不使用^$的话,对于\d{5,12}而言,使用这样的方法就只能保证字符串里包含5到12连续位数字,而不是整个字符串就是5到12位数字。

    字符转义

    如果你想查找元字符本身的话,比如你查找.,或者*,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用\来取消这些字符的特殊意义。因此,你应该使用\.\*。当然,要查找\本身,你也得用\\.

    例如:deerchao\.cn匹配deerchao.cnC:\\Windows匹配C:\Windows

    重复

    你已经看过了前面的*,+,{2},{5,12}这几个匹配重复的方式了。下面是正则表达式中所有的限定符(指定数量的代码,例如*,{5,12}等):

    表2.常用的限定符
    代码/语法说明
    *重复零次或更多次
    +重复一次或更多次
    ?重复零次或一次
    {n}重复n次
    {n,}重复n次或更多次
    {n,m}重复n到m次

    下面是一些使用重复的例子:

    Windows\d+匹配Windows后面跟1个或更多数字

    ^\w+匹配一行的第一个单词(或整个字符串的第一个单词,具体匹配哪个意思得看选项设置)

    字符类

    要想查找数字,字母或数字,空白是很简单的,因为已经有了对应这些字符集合的元字符,但是如果你想匹配没有预定义元字符的字符集合(比如元音字母a,e,i,o,u),应该怎么办?

    很简单,你只需要在方括号里列出它们就行了,像[aeiou]就匹配任何一个英文元音字母[.?!]匹配标点符号(.或?或!)

    我们也可以轻松地指定一个字符范围,像[0-9]代表的含意与\d就是完全一致的:一位数字;同理[a-z0-9A-Z_]也完全等同于\w(如果只考虑英文的话)。

    下面是一个更复杂的表达式:\(?0\d{2}[) -]?\d{8}

    这个表达式可以匹配几种格式的电话号码,像(010)88886666,或022-22334455,或02912345678等。我们对它进行一些分析吧:首先是一个转义字符\(,它能出现0次或1次(?),然后是一个0,后面跟着2个数字(\d{2}),然后是)-空格中的一个,它出现1次或不出现(?),最后是8个数字(\d{8})。

    (”和“)”也是元字符,后面的分组节里会提到,所以在这里需要使用转义。

    分枝条件

    不幸的是,刚才那个表达式也能匹配010)12345678(022-87654321这样的“不正确”的格式。要解决这个问题,我们需要用到分枝条件。正则表达式里的分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开。听不明白?没关系,看例子:

    0\d{2}-\d{8}|0\d{3}-\d{7}这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)

    \(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。你可以试试用分枝条件把这个表达式扩展成也支持4位区号的。

    \d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个例子是因为它能说明一个问题:使用分枝条件时,要注意各个条件的顺序。如果你把它改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。

    分组

    我们已经提到了怎么重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(后面会有介绍)。

    (\d{1,3}\.){3}\d{1,3}是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3}匹配1到3位的数字(\d{1,3}\.){3}匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3})。

    不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)

    理解这个表达式的关键是理解2[0-4]\d|25[0-5]|[01]?\d\d?,这里我就不细说了,你自己应该能分析得出来它的意义。

    IP地址中每个数字都不能大于255. 经常有人问我, 01.02.03.04 这样前面带有0的数字, 是不是正确的IP地址呢? 答案是: 是的, IP 地址里的数字可以包含有前导 0                (leading                zeroes).

    反义

    有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义

    表3.常用的反义代码
    代码/语法说明
    \W匹配任意不是字母,数字,下划线,汉字的字符
    \S匹配任意不是空白符的字符
    \D匹配任意非数字的字符
    \B匹配不是单词开头或结束的位置
    [^x]匹配除了x以外的任意字符
    [^aeiou]匹配除了aeiou这几个字母以外的任意字符

    例子:\S+匹配不包含空白符的字符串

    <a[^>]+>匹配用尖括号括起来的以a开头的字符串

    后向引用

    使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。

    呃……其实,组号分配还不像我刚说得那么简单:

    • 分组0对应整个正则表达式

    • 实际上组号分配过程是要从左向右扫描两遍的:第一遍只给未命名组分配,第二遍只给命名组分配--因此所有命名组的组号都大于未命名的组号

    • 你可以使用(?:exp)这样的语法来剥夺一个分组对组号分配的参与权.

    后向引用用于重复搜索前面某个分组匹配的文本。例如,\1代表分组1匹配的文本。难以理解?请看示例:

    \b(\w+)\b\s+\1\b可以用来匹配重复的单词,像go                go, 或者kitty kitty。这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字(\b(\w+)\b),这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符(\s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词)(\1)。

    你也可以自己指定子表达式的组名。要指定一个子表达式的组名,请使用这样的语法:(?<Word>\w+)(或者把尖括号换成'也行:(?'Word'\w+)),这样就把\w+的组名指定为Word了。要反向引用这个分组捕获的内容,你可以使用\k<Word>,所以上一个例子也可以写成这样:\b(?<Word>\w+)\b\s+\k<Word>\b

    使用小括号的时候,还有很多特定用途的语法。下面列出了最常用的一些:

    表4.常用分组语法
    分类代码/语法说明
    捕获(exp)匹配exp,并捕获文本到自动命名的组里
    (?<name>exp)匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)
    (?:exp)匹配exp,不捕获匹配的文本,也不给此分组分配组号
    零宽断言(?=exp)匹配exp前面的位置
    (?<=exp)匹配exp后面的位置
    (?!exp)匹配后面跟的不是exp的位置
    (?<!exp)匹配前面不是exp的位置
    注释(?#comment)这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

    我们已经讨论了前两种语法。第三个(?:exp)不会改变正则表达式的处理方式,只是这样的组匹配的内容不会像前两种那样被捕获到某个组里面,也不会拥有组号。“我为什么会想要这样做?”——好问题,你觉得为什么呢?

    零宽断言

    接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。最好还是拿例子来说明吧:

    断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。

    (?=exp)也叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I'm singing while you're                dancing.时,它会匹配singdanc

    (?<=exp)也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。比如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading

    假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:((?<=\d)\d{3})+\b,用它对1234567890进行查找时结果是234567890

    下面这个例子同时使用了这两种断言:(?<=\s)\d+(?=\s)匹配以空白符间隔的数字(再次强调,不包括这些空白符)

    负向零宽断言

    前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词--它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样:

    \b\w*q[^u]\w*\b匹配包含后面不是字母u的字母q的单词。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的\w*\b将会匹配下一个单词,于是\b\w*q[^u]\w*\b就能匹配整个Iraq fighting负向零宽断言能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:\b\w*q(?!u)\w*\b

    零宽度负预测先行断言(?!exp)断言此位置的后面不能匹配表达式exp。例如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词

    同理,我们可以用(?<!exp),零宽度负回顾后发断言断言此位置的前面不能匹配表达式exp(?<![a-z])\d{7}匹配前面不是小写字母的七位数字

    一个更复杂的例子:(?<=<(\w+)>).*(?=<\/\1>)匹配不包含属性的简单HTML标签内里的内容(?<=<(\w+)>)指定了这样的前缀被尖括号括起来的单词(比如可能是<b>),然后是.*(任意的字符串),最后是一个后缀(?=<\/\1>)。注意后缀里的\/,它用到了前面提过的字符转义;\1则是一个反向引用,引用的正是捕获的第一组,前面的(\w+)匹配的内容,这样如果前缀实际上是<b>的话,后缀就是</b>了。整个表达式匹配的是<b>和</b>之间的内容(再次提醒,不包括前缀和后缀本身)。

    注释

    小括号的另一种用途是通过语法(?#comment)来包含注释。例如:2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)

    要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。例如,我们可以前面的一个表达式写成这样:

          (?<=    # 断言要匹配的文本的前缀
          <(\w+)> # 查找尖括号括起来的内容
                  # (即HTML/XML标签)
          )       # 前缀结束
          .*      # 匹配任意文本
          (?=     # 断言要匹配的文本的后缀
          <\/\1>  # 查找尖括号括起来的内容
                  # 查找尖括号括起来的内容
          )       # 后缀结束

    贪婪与懒惰

    当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以这个表达式为例:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。

    有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:

    a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab(第一到第三个字符)ab(第四到第五个字符)

    为什么第一个匹配是aab(第一到第三个字符)而不是ab(第二到第三个字符)?简单地说,因为正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权——The                match                that begins earliest wins。

    表5.懒惰限定符
    代码/语法说明
    *?重复任意次,但尽可能少重复
    +?重复1次或更多次,但尽可能少重复
    ??重复0次或1次,但尽可能少重复
    {n,m}?重复n到m次,但尽可能少重复
    {n,}?重复n次以上,但尽可能少重复

    处理选项

    上面介绍了几个选项如忽略大小写,处理多行等,这些选项能用来改变处理正则表达式的方式。下面是.Net中常用的正则表达式选项:

    表6.常用的处理选项
    名称说明
    IgnoreCase(忽略大小写)匹配时不区分大小写。
    Multiline(多行模式)更改^$的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,$的精确含意是:匹配\n之前的位置以及字符串结束前的位置.)
    Singleline(单行模式)更改.的含义,使它与每一个字符匹配(包括换行符\n)。
    IgnorePatternWhitespace(忽略空白)忽略表达式中的非转义空白并启用由#标记的注释。
    ExplicitCapture(显式捕获)仅捕获已被显式命名的组。

    在C#中,你可以使用Regex(String, RegexOptions)构造函数来设置正则表达式的处理选项。如:Regex regex = new                Regex(@"\ba\w{6}\b",                RegexOptions.IgnoreCase);

    一个经常被问到的问题是:是不是只能同时使用多行模式和单行模式中的一种?答案是:不是。这两个选项之间没有任何关系,除了它们的名字比较相似(以至于让人感到疑惑)以外。事实上,为了避免混淆,在最新的                    JavaScript                    中,单行模式其实名叫 dotAll,意为点可以匹配所有字符,然而在指定该选项时,用的还是 Singleline 的首字母 s.

    目前(2019/06),只有基于 Webkit/Chromium 的浏览器(如 Chrome, Safari等)才支持 dotAll 选项。

    平衡组/递归匹配

    有时我们需要匹配像( 100 * ( 50 + 15 ) )这样的可嵌套的层次性结构,这时简单地使用\(.+\)则只会匹配到最左边的左括号和最右边的右括号之间的内容(这里我们讨论的是贪婪模式,懒惰模式也有下面的问题)。假如原来的字符串里的左括号和右括号出现的次数不相等,比如( 5 / ( 3 + 2 ) ) ),那我们的匹配结果里两者的个数也不会相等。有没有办法在这样的字符串里匹配到最长的,配对的括号之间的内容呢?

    这里介绍的平衡组语法是由.Net Framework支持的;其它语言/库不一定支持这种功能,或者支持此功能但需要使用不同的语法。

    为了避免(\(把你的大脑彻底搞糊涂,我们还是用尖括号代替圆括号吧。现在我们的问题变成了如何把xx <aa                        <bbb> <bbb> aa> yy这样的字符串里,最长的配对的尖括号内的内容捕获出来?

    这里需要用到以下的语法构造:

    • (?'group') 把捕获的内容命名为group,并压入堆栈(Stack)

    • (?'-group') 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败

    • (?(group)yes|no) 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分

    • (?!) 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败

    我们需要做的是每碰到了左括号,就在压入一个"Open",每碰到一个右括号,就弹出一个,到了最后就看看堆栈是否为空--如果不为空那就证明左括号比右括号多,那匹配就应该失败。正则表达式引擎会进行回溯(放弃最前面或最后面的一些字符),尽量使整个表达式得到匹配。

    <                   #最外层的左括号
      [^<>]*            #它后面非括号的内容
      (
          (
            (?'Open'<)  #左括号,压入"Open"
            [^<>]*      #左括号后面的内容
          )+
          (
            (?'-Open'>) #右括号,弹出一个"Open"
            [^<>]*      #右括号后面的内容
          )+
      )*
      (?(Open)(?!))     #最外层的右括号前检查
                        #若还有未弹出的"Open"
                        #则匹配失败
    
    >                #最外层的右括号

    平衡组的一个最常见的应用就是匹配HTML,下面这个例子可以匹配嵌套的<div>标签<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>.

    如果你不是一个程序员(或者你自称程序员但是不知道堆栈是什么东西),你就这样理解上面的三种语法吧:第一个就是在黑板上写一个"group",第二个就是从黑板上擦掉一个"group",第三个就是看黑板上写的还有没有"group",如果有就继续匹配yes部分,否则就匹配no部分。

    还有些什么东西没提到

    上边已经描述了构造正则表达式的大量元素,但是还有很多没有提到的东西。下面是一些未提到的元素的列表,包含语法和简单的说明。你可以在网上找到更详细的参考资料来学习它们--当你需要用到它们的时候。如果你安装了MSDN            Library,你也可以在里面找到.Net下正则表达式详细的文档。这里的介绍很简略,如果你需要更详细的信息,而又没有在电脑上安装MSDN Library,可以查看关于正则表达式语言元素的MSDN在线文档

    表7.尚未详细讨论的语法
    代码/语法说明
    \a报警字符(打印它的效果是电脑嘀一声)
    \b通常是单词分界位置,但如果在字符类里使用代表退格
    \t制表符,Tab
    \r回车
    \v竖向制表符
    \f换页符
    \n换行符
    \eEscape
    \0nnASCII代码中八进制代码为nn的字符
    \xnnASCII代码中十六进制代码为nn的字符
    \unnnnUnicode代码中十六进制代码为nnnn的字符
    \cNASCII控制字符。比如\cC代表Ctrl+C
    \A字符串开头(类似^,但不受处理多行选项的影响)
    \Z字符串结尾或行尾(不受处理多行选项的影响)
    \z字符串结尾(类似$,但不受处理多行选项的影响)
    \G当前搜索的开头
    \p{name}Unicode中命名为name的字符类,例如\p{IsGreek}
    (?>exp)贪婪子表达式
    (?<x>-<y>exp)平衡组
    (?im-nsx:exp)在子表达式exp中改变处理选项
    (?im-nsx)为表达式后面的部分改变处理选项
    (?(exp)yes|no)把exp当作零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式;否则使用no
    (?(exp)yes)同上,只是使用空表达式作为no
    (?(name)yes|no)如果命名为name的组捕获到了内容,使用yes作为表达式;否则使用no
    (?(name)yes)同上,只是使用空表达式作为no

    联系作者

    好吧,我承认,我骗了你,读到这里你肯定花了不止30分钟。相信我,这是我的错,而不是因为你太笨。我之所以说"30分钟",是为了让你有信心,有耐心继续下去。既然你看到了这里,那证明我的阴谋成功了。被忽悠的感觉很爽吧?

    要投诉我,或者觉得我其实可以忽悠得更高明,或者有关于正则表达式的问题, 可以发邮件到            deerchao#qq#com。如果本文给了你帮助,你可以使用支付宝或微信支付向我打赏。点击本页右上方的“打赏”即可看到支付二维码,可能你得先回到页面最顶端。

    网上的资源及本文参考文献

    更新纪录

    1. 2006-3-27 第一版

    2. 2006-10-12 第二版

      • 修正了几个细节上的错误和不准确的地方

      • 增加了对处理中文时的一些说明

      • 更改了几个术语的翻译(采用了MSDN的翻译方式)

      • 增加了平衡组的介绍

      • 放弃了对The Regulator的介绍,改用Regex Tester

    3. 2007-3-12 V2.1

      • 修正了几个小的错误

      • 增加了对处理选项(RegexOptions)的介绍

    4. 2007-5-28 V2.2

      • 重新组织了对零宽断言的介绍

      • 删除了几个不太合适的示例,添加了几个实用的示例

      • 其它一些微小的更改

    5. 2007-8-3 V2.21

      • 修改了几处文字错误

      • 修改/添加了对$,\b的精确说明

      • 承认了作者是个骗子

      • 给RegexTester添加了Singleline选项的相关功能

    6. 2008-4-13 v2.3

      • 调整了部分章节的次序

      • 修改了页面布局,删除了专门的参考节

      • 针对读者的反馈,调整了部分内容

    7. 2009-4-11 v2.3.1

      • 修改了几处文字错误

      • 添加了一些注释说明

      • 调整了一些措词

    8. 2011-8-17 v2.3.2

      • 更改了工具介绍,换用自行开发的正则表达式测试器

    9. 2013-1-10 v2.3.3

      • 说明包含前导0的IP地址是合法的

    10. 2017-6-6 v2.3.4

      • 更新测试工具

    11. 2017-6-12 v2.3.5

      • 修复分支条件章节下的错误(删除括号后的问号)

    12. 2019-6-28 v2.4

      • 提供在线 Javascript 正则表达式测试工具

      • 提到 Javascript 中的 dotAll 模式

      • 修改作者联系方式

    13. 2019-11-15 v2.4.1

      • 改进在手机浏览器下的页面布局

    d
    转自 deerchao 6 年前
    9,208

    场景:

    打印机只有 USB 接口,没有网线接口。

    方案:

    使用一台电脑连接打印机,并共享给局域网其它电脑。


    将连接打印机的电脑称为服务机,局域网其它电脑称为客户机。

    使用 Windows 7 作服务机比使用 Windows 10 更容易设置成功。 

    以下过程以使用 Windows 7 作服务机为例,Windows 10 的操作稍有不同。


    服务机:

    1. 安装打印机驱动,打印测试页成功。

    2. 在“设备和打印机”界面选中该打印机,右键属性,切换到“共享”,共享这台打印机。

      image.png

    3. 在“计算机管理”中打开“用户”,点击 Guest 属性,将“帐户已禁用”前的勾去掉。

      image.png

    4. 打开“本地安全策略”(命令:secpol.msc),在 安全设置-本地策略-用户权限分配 中选中“拒绝从网络访问这台计算机”,将“Guest”用户删除。

      image.png

      如果使用打印机的人数超过10人,需要在 安全设置-本地策略-安全选项 中将“交互式登录:之前登录到缓存的次数(域控制器不可用时)”改大。

    5. 将“睡眠”关闭


    客户机

    1. 以 Windows 10 为例,打开“我的电脑”或“计算机”或“此电脑”,点击菜单中的“网络”

      image.png

      双击打开打印机所在的计算机(或直接在地址栏输出入“\\192.168.1.*”即服务机的 IP 地址)

    2. 正常情况下,打开后显示打印机图标,右键点击“连接”后安装驱动即可正常使用。

    3. 如果提示以下错误 0x80070035

      image.png

      打开注册表(命令:regedit),定位到:

      计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters

      修改 AllowInsecureGuestAuth 值为 1,若没有找到,创建一个就行了,类型是 DWORD。

      image.png

    4. 如果“网络”中不显示该计算机图标,试试直接输入IP地址,仍然打不开的话,我还不知道怎么搞,可能是公用/私有网络的缘故,或者家庭网络/工作网络的区别,请自行百度解决。


    扩展设置

    设置电脑自动开机

    以某 Dell 为例,开机进入 BIOS:

    QQ图片20220922085704.jpg


    设置电脑自动关机

    这里借助一款比较老牌又好用的关机助手

    image.pngimage.pngimage.png

    此处设置开机时自动运行关机助手,如果不生效,直接在系统启程程序中添加快捷方式

    另外,需要删除开机密码。

    xoyozo 3 年前
    2,496

    如果富士施乐 DocuPrint P118 w 打印机开启后电脑无法连接,但路由器上显示已连网,很有可能是 IP 已变更(增添新设备后抢占了打印机原来的 IP 地址)。

    这时候需要重新安装打印机驱动:(可能有不需要重装驱动的方法)

    富士施乐 DocuPrint P118 w 在安装驱动时需要路由器支持 WPS/AOSS,

    但小米路由器 Pro 中找不到该功能,而且实测无线方式安装打印机驱动失败。

    我的方法是,找一台 TP-LINK 路由器,

    将 SSID 和 WIFI 密码改成与小米路由器相同,

    关闭小米路由器,

    电脑连接这个 SSID,

    然后按无线的方式安装打印机驱动程序,

    完成后,关闭 TP-LINK 路由器,打开小米路由器,

    网络启动后,重启打印机。

    (打印时可能需要在窗口中选择另一个打印机名称)

    xoyozo 4 年前
    4,803

    打开 Adobe Acrobat Pro,依次点击菜单的 文件 - 创建 - 将文件合并为单个 PDF

    image.png

    添加文件并合并文件

    image.png

    在打印页面选择“多页”、“1×2”、“纵向”,在右侧观察预览

    image.png

    打印。

    xoyozo 5 年前
    18,692

    一、显示信息的命令

    console.log("normal");           // 用于输出普通信息
    console.info("information");     // 用于输出提示性信息
    console.error("error");          // 用于输出错误信息
    console.warn("warn");            // 用于输出警示信息

     

    二、点位符:字符(%s)、整数(%d或%i)、浮点数(%f)和对象(%o);

    console.log("%s","string");                 //字符(%s)
    console.log("%d年%d月%d日",2016,8,29);       //整数(%d或%i)
    console.log("圆周率是%f",3.1415926);         //浮点数(%f)
    var dog = {};
    dog.name = "大毛";
    dog.color = "黄色";
    dog.sex = "母狗";
    console.log("%o",dog);                      //对象(%o)


     

    三、信息分组 (console.group(),console.groupEnd())

    console.group("第一组信息");
        console.log("第一组第一条:我的博客");
        console.log("第一组第二条:CSDN");
    console.groupEnd();
    
    console.group("第二组信息");
        console.log("第二组第一条:程序爱好者QQ群");
        console.log("第二组第二条:欢迎你加入");
    console.groupEnd();

     

     

    四、将对象以树状结构展现 (console.dir()可以显示一个对象所有的属性和方法)

    var info = {
        name : "Alan",
        age : "27",
        grilFriend : "nothing",
        getName : function(){
            return this.name;
        }
    }
    console.dir(info);


     

    五、显示某个节点的内容 (console.dirxml()用来显示网页的某个节点(node)所包含的html/xml代码)

    var node = document.getElementById("info");
    node.innerHTML += "<p>追加的元素显示吗</p>";
    console.dirxml(node);

     

    六、判断变量是否是真 (console.assert()用来判断一个表达式或变量是否为真,只有表达式为false时,才输出一条相应作息,并且抛出一个异常)

    var testObj = false;
    console.assert(testObj, '当testObj为false时才输出!');

     

    七、计时功能 (console.time()和console.timeEnd(),用来显示代码的运行时间)

    console.time("控制台计时器");
    for(var i = 0; i < 10000; i++){
        for(var j = 0; j < 10000; j++){}       
    }
    console.timeEnd("控制台计时器");

     

    八、性能分析performance profile (就是分析程序各个部分的运行时间,找出瓶颈所在,使用的方法是console.profile()和console.proileEnd();) 

    function All(){
        // alert(11);
        for(var i = 0; i < 10; i++){
            funcA(100);
        }
        funcB(1000);
    }
    function funcA(count){
        for(var i = 0; i < count; i++){};
    }
    function funcB(count){
        for(var i = 0; i < count; i++){};
    }
    console.profile("性能分析器");
    All();
    console.profileEnd();

    详细的信息在chrome控制台里的"profile"选项里查看

     

    九、console.count()统计代码被执行的次数

    function myFunction(){
        console.count("myFunction 被执行的次数");
    }
    myFunction();       //myFunction 被执行的次数: 1
    myFunction();       //myFunction 被执行的次数: 2
    myFunction();       //myFunction 被执行的次数: 3


     

    十、keys和values,要在浏览器里输入

     

     

    十一、console.table表格显示方法

      var mytable = [
        {
            name: "Alan",
            sex : "man",
            age : "27"
        },
        {
            name: "Wu",
            sex : "gril",
            age : "28"
        },
        {
            name: "Tao",
            sex : "man and gril",
            age : "29"
        }
    ]
    console.table(mytable);


     

    十二、Chrome 控制台中原生支持类jQuery的选择器,也就是说你可以用$加上熟悉的css选择器来选择DOM节。

    $("body");           //选择body节点

     

    十三、copy通过此命令可以将在控制台获取到的内容复制到剪贴板

    copy(document.body);                      //复制body
    copy(document.getElementById("info"));    //复制某id元素的的节点

     

    十四、$_命令返回最近一次表达式执行的结果,$0-$4代表了最近5个你选择过的DOM节点

     

     

    十五、利用控制台输出文字,图片,以%c开头,后面的文字就打印的信息,后面一个参数就是样式属性;

    console.log("请在邮件中注明%c 来自:console","font-size:16px;color:red;font-weight:bold;");

     


    A
    转自 AlanTao 5 年前
    3,886

    〓 系统

    功能命令--help示例
    关机halt
    halt
    重启reboot
    reboot
    系统监视器top系统时间, 运行天数, 当前登录用户数, 系统负载
    总进程数, 运行中的, 睡眠的, 停止的, 未响应的
    Cpu(s):us 用户, sy 系统, ni XX, id 空闲, wa 等待, hi XX, si XX
    Mem, 已使用, 空余, 缓冲
    Swap, 已使用, 空余, 缓冲
    快捷键:
    M 按占内存排序
    P 按占Cpu排序
    1 显示每个 Cpu
    k 杀死进程
    q 退出
    top
    查看进程psaux
    -ef
    列出包含 java 的进程
    ps aux |grep java 
    ps -ef |grep java
    查看内存及 Swap 用量free-b,-k,-m,-g 按单位显示free -m
    查看系统时间date   显示 CST 时间
    -R 显示时区
    -u 显示 UTC 时间
    date
    查看硬件时间clock
    clock
    设置系统日期

    date -s 月/日/年
    设置系统时间

    date -s 时:分:秒
    将系统时间写入到硬件时间

    clock -w
    查看系统版本

    cat /etc/*release
    升级系统软件

    yum update -y

    〓 文件

    功能命令--help示例
    进入目录cd
    cd .. # 上一层目录
    cd /root # 根目录
    列出目录ls白色:表示普通文件
    蓝色:表示目录
    绿色:表示可执行文件
    红色:表示压缩文件
    浅蓝色:链接文件
    红色闪烁:表示链接的文件有问题
    黄色:表示设备文件
    灰色:表示其他文件
    ls
    创建目录mkdir
    mkdir XXX
    删除目录rm
    rm -rf XXX
    删除文件rm
    rm XXX
    复制文件cp
    cp XXX YYY
    复制目录cp-r 复制目录及目录内的所有项目
    -v 详细显示进行的步骤
    cp -rv XXX YYY
    重命名文件mv-i: 若指定目录已有同名文件,则先询问是否覆盖旧文件;
    -f: 在mv操作要覆盖某已有的目标文件时不给任何指示;
    mv 源文件 目标文件
    移动文件mvmv 一个或多个文件 目标目录
    下载文件wget下载到当前目录wget http://XXX.tar.gz
    计算文件/目录的磁盘用量du-a 不仅显示目录,同时显示文件
    -h 容易阅读方式显示
    --max-depth=N 可指定计算深度
    du -ah --max-depth=1 | sort -n
    查找文件find
    find /home -name *.apk

    〓 tar

    功能命令--help示例
    tartar-z 是否压缩
    -c 打包
    -x 解包
    -v 详细地列出处理的文件
    -f 
    打包:tar -cvf abc.tar abc
    解包:tar -xvf abc.tar
    压缩打包:tar -zcvf abc.tar.gz abc
    解压解包:tar -zxvf abc.tar.gz

    〓 磁盘

    功能命令--help示例
    查看所有磁盘及分区fdisk -l
    fdisk -l
    查看当前挂载df-h 按可阅读的方式打印数值和单位
    -T 显示文件系统类型
    df -hT
    管理磁盘分区fdisk /dev/***进入后的操作说明:
    m 显示命令菜单
    d 删除一个分区
    n 创建一个分区(e 扩展分区;p 主分区)
    t 改变分区ID
    q 不保存退出
    w 保存退出
    fdisk /dev/vdb
    格式化分区mkfs.*** /dev/***N
    mkfs.xfs /dev/vdb1
    挂载分区mount /dev/***N /***
    mount /dev/vdb1 /www
    卸载分区umount /dev/***N
    umount /dev/vdb1
    开机自动挂载vi /etc/fstab配置文档格式:设备 挂载点 文件系统类型 defaults 0 0打开:vi /etc/fstab
    配置:/dev/vdb1 /www xfs defaults 0 0

    〓 网络

    功能命令--help示例
    查看 IP 配置ifconfig
    ifconfig
    配置网卡 IP
    配置文件目录:/etc/sysconfig/network-scripts/
    配置文件格式:
    DEVICE=eth0 / eth0:0 / ... # 在配置多线时若使用 cp 命令复制配置文件,必须修改此项以防止冲突
    HWADDR=XX:XX:XX:XX:XX:XX # 网卡地址
    TYPE=Ethernet # 以太网
    UUID=********
    ONBOOT=yes # 开机启动
    NM_CONTROLLED=yes
    BOOTPROTO=static # 使用静态 IP
    IPADDR=192.168.1.2 # IP 地址
    NETMASK=255.255.255.XXX # 子网掩码
    GATEWAY=192.168.1.1 # 网关
    DNS1=114.114.114.114
    DNS2=8.8.8.8
    vi ifcfg-XXXN(:N)
    重启网卡
    使配置生效service network restart

    〓 防火墙

    功能命令--help示例
    配置 iptables
    添加需要允许的端口的方法同 22 端口vi /etc/sysconfig/iptables
    重启使配置生效

    service iptables restart

    〓 用户/权限

    功能命令--help示例
    添加用户useradd-g 组名 # 加入到该组
    -s /bin/false #不允用户直接登录系统
    useradd –g 组名 用户名 -s /bin/false
    修改密码passwd
    passwd 用户
    查看所有用户

    cut -d : -f 1 /etc/passwd 
    查看可以登录系统的用户

    cat /etc/passwd | grep -v /sbin/nologin | cut -d : -f 1
    删除用户
    -r, --remove                  remove home directory and mail spooluserdel 用户
    添加用户组groupadd
    groupadd 组名
    为组添加用户(用户必须已存在)gpasswd
    gpasswd -a 用户 组
    将用户移出组gpasswd
    gpasswd -d 用户 组
    查看用户所属组groups
    groups 用户
    查看组中有哪些用户groupmems
    groupmems -g 组 -l
    更改文件/目录所有者chown-R 递归处理所有的文件及子目录chown -R 用户:组 ***
    更改文件/目录权限chmod-R 以递归方式更改所有的文件及子目录chmod -R 777 ***

    〓 vi 编辑器

    功能命令--help示例
    打开文件vi
    vi XXX
    进入编辑模式
    按 a/i/o/Insert 等
    进入末行模式/命令模式
    按 Esc后:
    :w 保存不退出
    :q 退出(提示是否保存)
    :wq 保存并退出
    :w XXX 另存到文件 XXX
    :q! 不保存退出

    〓 计划任务

    功能命令--help示例
    设置计划任务crontab详细步骤见本页底部crontab -l # 查看计划任务
    crontab -e # 编辑计划任务

    〓 网站

    功能命令--help示例
    简单审查日志cat | grep
    cat 日志文件 | grep 关键词1 | grep 关键词2 | more
    日志分析goaccess
    见下文

    〓 goaccess

    功能命令--help示例
    安装yum install goaccess

    日志格式NCSA Commbined Log Format
    date_format %d/%b/%Y
    log_format %h %^[%d:%^] "%r" %s %b "%R" "%u"
    参数-f需要解析的日志文件
    参数-e指定 IP 地址统计
    参数-p指定配置文件可以将上面的日志格式内容保存到文件 ~/.goacessrc
    参数-H显示 HTTP 协议信息
    参数-M显示 HTTP 方法信息
    生成文件

    goaccess -f 日志文件 -p ~/.goaccessrc > 目标文件.htm

    〓 lnmp

    功能命令--help示例
    重启 LNMP

    /root/lnmp restart
    重启 MySQL

    /etc/init.d/mysql restart
    重启 PureFTPd

    /root/pureftpd restart
    安装 LNMP
    http://lnmp.org/install.html 
    常见问题
    http://lnmp.org/faq.html 
    状态管理命令
    http://lnmp.org/faq/lnmp-status-manager.html 
    相关软件目录及文件位置
    http://lnmp.org/faq/lnmp-software-list.html 
    防跨站、跨目录安全设置(仅支持 PHP 5.3.3 以上版本)
    http://www.vpser.net/security/lnmp-cross-site-corss-dir-security.html 
    查看 Nginx 版本

    nginx -V
    查看 MySQL 版本

    mysql -V
    查看 PNP 版本

    php -v
    查看 Apache 版本

    httpd -v
    查内存

    cat /proc/meminfo
    php.ini

    vim /usr/local/php/etc/php.ini
    MySQL 配置文件

    vim /etc/my.cnf
    添加网站

    /root/vhost.sh
    添加 ProFTPd 用户

    /root/proftpd_vhost.sh

    〓 nginx

    功能命令--help示例
    启动/停止/重启service nginx

    /etc/rc.d/init.d/nginx

    service nginx start
    service nginx stop
    service nginx restart
    伪静态在 .conf 文件中配置
    rewrite ^(.*)/read-htm-(.*)\.html(.*)$ $1/read.php?$2.html? last;
    rewrite ^(.*)/thread-htm-(.*)\.html(.*)$ $1/thread.php?$2.html? last;
    rewrite ^(.*)-htm-(.*)$ $1.php?$2 last;
    rewrite ^(.*)/simple/([a-z0-9\_]+\.html)$ $1/simple/index.php?$2 last;
    rewrite ^(.*)/data/(.*)\.(htm|php)$ 404.html last;
    rewrite ^(.*)/attachment/(.*)\.(htm|php)$ 404.html last;
    rewrite ^(.*)/html/(.*)\.(htm|php)$ 404.html last;
    防盗链在 .conf 文件中配置HttpRefererModule location ~* \.(gif|jpg|png|swf|flv)$
    {
    valid_referers none blocked *.0574bbs.com *.eyuyao.com 0574bbs.com eyuyao.com;
    if ($invalid_referer)
    {
    rewrite ^/ http://web1.eyuyao.com/yyad/src/3122.jpg;
    # return 404;
    }
    }
    浏览器缓存在 .conf 文件中配置
    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
    {
    expires 30d;
    }

    〓 vsftpd

    功能命令--help示例
    安装

    yum install vsftpd
    查看是否已安装

    rpm -q vsftpd
    启动/停止/重启service vsftpd
    service vsftpd start
    service vsftpd stop
    service vsftpd restart
    配置文件

    vi /etc/vsftpd/vsftpd.conf

    〓 MySQL

    功能命令--help示例
    登录mysql
    mysql -u username -p
    登出

    exit
    查看信息

    status;
    查询当前正在执行的 SQL 语句

    show processlist;
    删除指定时间之前的日志PURGE
    PURGE MASTER LOGS BEFORE '2015-1-1 0:00:00';

    〓 scp 远程文件/目录传输命令 (yum install openssh-clients) 用法

    scp 会把文件权限(读取/写入/执行)带过来,但所有者为当前执行 scp 命令的用户。

    scp 低版本有许多漏洞,用完最好 yum remove openssh-clients

    scp 采用直接覆盖的机制,如需判断文件无差异则跳过,应改用 rsync 命令。查看 rsync 详细使用方式及与 scp 对比

    功能命令--help示例
    若远程服务器 SSH 端口非默认scp-P 端口号
    下载远程服务器上的文件到本地scp
    scp 远程用户@远程服务器:远程文件 本地文件
    下载远程服务器上的目录到本地scp

    -P 端口

    -v 显示进度

    -r 递归

    scp -r 远程用户@远程服务器:远程目录 本地目录

    实例:scp -r root@x.x.x.x:/a/b/ /c/d/

    结果:/c/d/b/,即将整个 b 复制到 d 下(注意与 rsync 命令的区别)


    本地文件上传到远程服务器scp
    scp 本地文件 远程用户@远程服务器:远程文件
    本地目录上传到远程服务器scp最终目录结构参:远程->本地scp -r 本地目录 远程用户@远程服务器:远程目录

    〓 rsync 远程文件/目录传输命令 (yum install rsync) 用法查看 rsync 详细使用方式及与 scp 对比

    rsync 会把文件权限(读取/写入/执行)带过来,所有者也会带过来。

    相比于 scp 最大的优势就是可以增量同步

    功能命令--help示例
    下载远程服务器上的目录到本地rsync

    -a 递归

    -v 详细

    -p, -- perms 保持权限

    -g, -- group 保持属组

    -o, --owner 保持属主

    -r 递归

    --progress 打印

    --delete 删除已不存在的文件

    -u 表示仅更新较新的文件

    -z 表示在传输过程中进行压缩

    -e 'ssh -p 2222' 指定其它端口

    rsync 远程用户@远程服务器:远程目录 本地目录

    实例:rsync -avu --progress root@x.x.x.x:/a/b/ /c/d/

    结果:/c/d/,即将 b 内的文件(夹)复制到 d 下(注意与 scp 命令的区别)


    本地文件上传到远程服务器rsync
    rsync 本地文件 远程用户@远程服务器:远程文件
    本地目录上传到远程服务器rsync最终目录结构参:远程->本地rsync 本地目录 远程用户@远程服务器:远程目录

    〓 ftp 客户端 (yum install ftp)

    功能命令--help示例
    登录ftp
    ftp 目标服务器
    列出远程当前路径目录/文件ls
    ls
    创建远程目录mkdir
    mkdir 目录名
    删除远程目录(空)rmrmdir
    mkdir 目录名
    进入远程目录cd
    cd 目录名
    显示远程当前路径pwd
    pwd
    重命名远程文件rename
    rename 原文件名 新文件名
    上传文件put
    put 本地文件名
    下载文件get
    get 远程文件名
    批量下载文件mget需要单个确认
    批量下载文件【lftp】mirror参数有很多mirror
    返回 shell(不退出)!
    !
    返回 ftp(接上步)exit
    ftp

    exit
    ftp
    结束bye
    quit

    bye
    quit

    〓 iftop

    流量监控工具 教程 

    〓 GoAccess

    实时网站日志分析工具 官网 

    〓 Cacti

    网络流量监测图形分析工具 官网  百科 

    常见问题笔记

    加硬盘

    • 插入新硬盘

    • 若有 RAID,则先设置,使操作系统能认到硬盘

    • 使用 fdisk 命令对新设备进行分区

    • 使用 mkfs 命令对新分区进行格式化

    • 使用 mount 命令进行挂载

    • 设置开机自动挂载(vi /etc/fstab)

    更改 MySQL 数据库目录位置

    • 停止 MySQL 服务

    • 将原数据目录转移或复制到新位置(若是复制,则修改所有者使原来一致)

    • 找到 my.cnf 配置文件(一般在 /etc/),修改 datadir 值为新路径

    • 启动 MySQL 服务

    502 Bad Gateway 问题排查

    • 查看 PHP 日志,路径:/usr/local/php/var/log

    • 一般为“server reached pm.max_children setting (10), consider raising it”连接数问题,在“/usr/local/php/etc”下的所有配置文件中查找并修改相关设置即可(如改成 1000)。

    计划任务(实例:定时备份数据库并通过 FTP 同步至其它服务器)

    • 创建可执行文件:vi dotask.sh

    • dotask.sh 的内容示例:

      DATE_TIME=`date +%Y_%m_%d_%H%M%S`;
      FILE_NAME=数据库名_backup_$DATE_TIME.sql;
      cd /home/mysqlbackup/;
      mysqldump -u数据库用户名 -p数据库密码 数据库名>$FILE_NAME;
      tar -zcf $FILE_NAME.tar.gz $FILE_NAME;
      rm $FILE_NAME;

      ftp -v -n FTP地址 << END
      user FTP用户名 FTP密码
      bin
      put 本地目录文件 目标路径文件
      bye
      END


      文件名乱码问题可以在行末加“;”来解决

    • 赋予执行权限:chmod 777 dotask.sh (ls 命令时呈绿色)

    • 编辑计划任务:crontab -e

    • crontab 书写规则:

      # 分 时 日 月 周 文件路径
      0 3 * * * /home/dotask.sh
      30 4 * * * /home/dotask2.sh


      更多帮助 

    • 重启 crond:/etc/init.d/crond restart

    netstat

    • netstat -an | grep xxx.xxx.xxx.xxx 可查看此 IP 的 TCP 请求及端口

    xoyozo 6 年前
    5,312

    公众号的报警群中经常收到这样的消息:

    Appid: *
    昵称: *
    时间: *
    内容: 微信服务器向公众号推送消息或事件后,开发者5秒内没有返回
    次数: *分钟 *次
    错误样例: [OpenID=*][Stamp=*][3rdUrl=http://api.socialbase.cn/extapi/wechat/message/*/][IP=*][Event=Click Menu Url]
    报警排查指引,请见: http://url.cn/ab0jnP

    除此之外还有另外一个问题,就是客户端发送图片后会收到两条回复,一条是“印美图”处理打印图片链接,另一条是识脸判断年龄的图文。前者是之前与印美图公司合作的与快速打印照片设备配合吸粉的功能,后者是跳转到使用 FACE++ 的页面显示图片中人物的年龄。

    blob.png blob.png

    几天来一直找不出原因,代码中搜索错误关键词无果,被动回复消息整个流程断点逐步调试也没有发现哪里有返回“请点击这里预览印美图 点击此处裁剪相片并打印。”

    不是开发模式的问题,那一定在公众平台,登录后终于在“开发”的“基本配置”页找到了“管理已授权的第三方平台”,找到“Socialbase”取消授权即可。

    blob.png

    整个世界安静了……

    blob.png

    xoyozo 7 年前
    16,670

    前言

      最近在学习Web Api框架的时候接触到了async/await,这个特性是.NET 4.5引入的,由于之前对于异步编程不是很了解,所以花费了一些时间学习一下相关的知识,并整理成这篇博客,如果在阅读的过程中发现不对的地方,欢迎大家指正。

    同步编程与异步编程

      通常情况下,我们写的C#代码就是同步的,运行在同一个线程中,从程序的第一行代码到最后一句代码顺序执行。而异步编程的核心是使用多线程,通过让不同的线程执行不同的任务,实现不同代码的并行运行。

    前台线程与后台线程

      关于多线程,早在.NET2.0时代,基础类库中就提供了Thread实现。默认情况下,实例化一个Thread创建的是前台线程,只要有前台线程在运行,应用程序的进程就一直处于运行状态,以控制台应用程序为例,在Main方法中实例化一个Thread,这个Main方法就会等待Thread线程执行完毕才退出。而对于后台线程,应用程序将不考虑其是否执行完毕,只要应用程序的主线程和前台线程执行完毕就可以退出,退出后所有的后台线程将被自动终止。来看代码应该更清楚一些:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace ConsoleApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("主线程开始");
    
                //实例化Thread,默认创建前台线程
                Thread t1 = new Thread(DoRun1);
                t1.Start();
    
                //可以通过修改Thread的IsBackground,将其变为后台线程
                Thread t2 = new Thread(DoRun2) { IsBackground = true };
                t2.Start();
    
                Console.WriteLine("主线程结束");
            }
    
            static void DoRun1()
            {
                Thread.Sleep(500);
                Console.WriteLine("这是前台线程调用");
            }
    
            static void DoRun2()
            {
                Thread.Sleep(1500);
                Console.WriteLine("这是后台线程调用");
            }
        }
    }

      运行上面的代码,可以看到DoRun2方法的打印信息“这是后台线程调用”将不会被显示出来,因为应用程序执行完主线程和前台线程后,就自动退出了,所有的后台线程将被自动终止。这里后台线程设置了等待1.5s,假如这个后台线程比前台线程或主线程提前执行完毕,对应的信息“这是后台线程调用”将可以被成功打印出来。

    Task

      .NET 4.0推出了新一代的多线程模型Task。async/await特性是与Task紧密相关的,所以在了解async/await前必须充分了解Task的使用。这里将以一个简单的Demo来看一下Task的使用,同时与Thread的创建方式做一下对比。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace TestApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("主线程启动");
                
                //.NET 4.5引入了Task.Run静态方法来启动一个线程
                Task.Run(() => { Thread.Sleep(1000); Console.WriteLine("Task1启动"); });
    
                //Task启动的是后台线程,假如要在主线程中等待后台线程执行完毕,可以调用Wait方法
                Task task = Task.Run(() => { Thread.Sleep(500); Console.WriteLine("Task2启动"); });
                task.Wait();
                
                Console.WriteLine("主线程结束");
            }
        }
    }

      首先,必须明确一点是Task启动的线程是后台线程,不过可以通过在Main方法中调用task.Wait()方法,使应用程序等待task执行完毕。Task与Thread的一个重要区分点是:Task底层是使用线程池的,而Thread每次实例化都会创建一个新的线程。这里可以通过这段代码做一次验证:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace TestApp
    {
        class Program
        {
            static void DoRun1()
            {
                Console.WriteLine("Thread Id =" + Thread.CurrentThread.ManagedThreadId);
            }
    
            static void DoRun2()
            {
                Thread.Sleep(50);
                Console.WriteLine("Task调用Thread Id =" + Thread.CurrentThread.ManagedThreadId);
            }
    
            static void Main(string[] args)
            {
                for (int i = 0; i < 50; i++)
                {
                    new Thread(DoRun1).Start();
                }
    
                for (int i = 0; i < 50; i++)
                {
                    Task.Run(() => { DoRun2(); });
                }
    
                //让应用程序不立即退出
                Console.Read();
            }
        }
    }

      运行代码,可以看到DoRun1()方法每次的Thread Id都是不同的,而DoRun2()方法的Thread Id是重复出现的。我们知道线程的创建和销毁是一个开销比较大的操作,Task.Run()每次执行将不会立即创建一个新线程,而是到CLR线程池查看是否有空闲的线程,有的话就取一个线程处理这个请求,处理完请求后再把线程放回线程池,这个线程也不会立即撤销,而是设置为空闲状态,可供线程池再次调度,从而减少开销。

    Task<TResult>

      Task<TResult>是Task的泛型版本,这两个之间的最大不同是Task<TResult>可以有一个返回值,看一下代码应该一目了然: 

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace TestApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("主线程开始");
    
                Task task = Task.Run(() => { Thread.Sleep(1000); return Thread.CurrentThread.ManagedThreadId.ToString(); });
                Console.WriteLine(task.Result);
    
                Console.WriteLine("主线程结束");
            }
        }
    }

      Task<TResult>的实例对象有一个Result属性,当在Main方法中调用task.Result的时候,将等待task执行完毕并得到返回值,这里的效果跟调用task.Wait()是一样的,只是多了一个返回值。

    async/await 特性

      经过前面的铺垫,终于迎来了这篇文章的主角async/await,还是先通过代码来感受一下这两个特性的使用。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace TestApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("-------主线程启动-------");
                Task task = GetLengthAsync();
                Console.WriteLine("Main方法做其他事情");
                Console.WriteLine("Task返回的值" + task.Result);
                Console.WriteLine("-------主线程结束-------");
            }
    
            static async Task GetLengthAsync()
            {
                Console.WriteLine("GetLengthAsync Start");  
                string str = await GetStringAsync();
                Console.WriteLine("GetLengthAsync End");
                return str.Length;
            }
    
            static Task GetStringAsync()
            {
                return Task.Run(() => { Thread.Sleep(2000); return "finished"; });
            }
        }
    }

      首先来看一下async关键字。async用来修饰方法,表明这个方法是异步的,声明的方法的返回类型必须为:void或Task或Task<TResult>。返回类型为Task的异步方法中无需使用return返回值,而返回类型为Task<TResult>的异步方法中必须使用return返回一个TResult的值,如上述Demo中的异步方法返回一个int。而返回类型可为void,则是为了和事件处理程序兼容,比如下面的示例:

    public Form1()
    {
        InitializeComponent();
        btnDo.Click += Down;
    }
    
    public async void Down(object sender, EventArgs e)
    {
        btnDo.Enabled = false;
        string str = await Run();
        labText.Text = str;
        btnDo.Enabled = true;
    }
    
    public Task Run()
    {
        return Task.Run(() => { Thread.Sleep(5000); return DateTime.Now.ToString(); });
    }

      再来看一下await关键字。await必须用来修饰Task或Task<TResult>,而且只能出现在已经用async关键字修饰的异步方法中。

      通常情况下,async/await必须成对出现才有意义,假如一个方法声明为async,但却没有使用await关键字,则这个方法在执行的时候就被当作同步方法,这时编译器也会抛出警告提示async修饰的方法中没有使用await,将被作为同步方法使用。了解了关键字async\await的特点后,我们来看一下上述Demo在控制台会输入什么吧。

      

      输出的结果已经很明确地告诉我们整个执行流程了。GetLengthAsync异步方法刚开始是同步执行的,所以"GetLengthAsync Start"字符串会被打印出来,直到遇到第一个await关键字,真正的异步任务GetStringAsync开始执行,await相当于起到一个标记/唤醒点的作用,同时将控制权放回给Main方法,"Main方法做其他事情"字符串会被打印出来。之后由于Main方法需要访问到task.Result,所以就会等待异步方法GetLengthAsync的执行,而GetLengthAsync又等待GetStringAsync的执行,一旦GetStringAsync执行完毕,就会回到await GetStringAsync这个点上执行往下执行,这时"GetLengthAsync End"字符串就会被打印出来。

      当然,我们也可以使用下面的方法完成上面控制台的输出。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace TestApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("-------主线程启动-------");
                Task task = GetLengthAsync();
                Console.WriteLine("Main方法做其他事情");
                Console.WriteLine("Task返回的值" + task.Result);
                Console.WriteLine("-------主线程结束-------");
            }
    
            static Task GetLengthAsync()
            {
                Console.WriteLine("GetLengthAsync Start");
                Task task = Task.Run(() => { string str = GetStringAsync().Result; 
                    Console.WriteLine("GetLengthAsync End"); 
                    return str.Length; });           
                return task;
            }
    
            static Task GetStringAsync()
            {
                return Task.Run(() => { Thread.Sleep(2000); return "finished"; });
            }
        }
    }

      对比两种方法,是不是async\await关键字的原理其实就是通过使用一个线程完成异步调用吗?答案是否定的。async关键字表明可以在方法内部使用await关键字,方法在执行到await前都是同步执行的,运行到await处就会挂起,并返回到Main方法中,直到await标记的Task执行完毕,才唤醒回到await点上,继续向下执行。更深入点的介绍可以查看文章末尾的参考文献。

    async/await 实际应用

      微软已经对一些基础类库的方法提供了异步实现,接下来将实现一个例子来介绍一下async/await的实际应用。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Net;
    
    namespace TestApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("开始获取博客园首页字符数量");
                Task task1 = CountCharsAsync("http://www.cnblogs.com");
                Console.WriteLine("开始获取百度首页字符数量");
                Task task2 = CountCharsAsync("http://www.baidu.com");
    
                Console.WriteLine("Main方法中做其他事情");
    
                Console.WriteLine("博客园:" + task1.Result);
                Console.WriteLine("百度:" + task2.Result);
            }
    
            static async Task CountCharsAsync(string url)
            {
                WebClient wc = new WebClient();
                string result = await wc.DownloadStringTaskAsync(new Uri(url));
                return result.Length;
            }
        }
    }

    参考文献:<IIIustrated C# 2012>     关于async/await的FAQ    《深入理解C#》

    t
    转自 teroy 8 年前
    4,114

    海鲜大部分是冷冻包装产品,小部分是散装称重产品,与超市类似,应选择“商超版”,除了钱箱、扫描枪,还应接电子秤。

    • 触摸屏

      不用记键盘快捷键,商品少的话直接点选,连扫码、输码、搜索都省了

    • 好用的收银软件

      见下方表格

    • 称重一体

      散装商品方便取重

    • Windows 7 及以上版本 或 安卓 操作系统

      安心连网,用于支付宝/微信收款,方便连锁店共享数据。选择 Windows 的话建议选到 win10,因为 win7 将在 2020 年停止支持;新的收银机基本用的安卓,可能因为各种成本低吧,不过听说安卓收银机会卡顿,类似于安卓手机的操作感,核心数和内存跟PC也不是一个概念。

    • 固态硬盘

      Windows 建议选固态,开机快;安卓是闪存

    • 销售过程中任何时间使用会员卡,而不是必须先扫会员卡,再扫商品

    • 标签打印机,支持打印价格签(不干胶热敏纸条码),称重商品需要贴上即时生成的条码

    • 支持扫描枪或扫码器扫描客户手机上的支付宝、微信(动态)付款码,以规避客户使用支付成功的截图来欺骗。需要一些配置,提现扣手续费。

    • 支持拼音简码销售


    选购步骤 先选择适合自己的收银软件,再选择配置和外观,当然要考虑外接设备的兼容性(电子秤同步称重、软件开钱箱、是否需要标签打印机或后厨打印机、扫描枪是否需要支持支付宝微信 等等)。

    注:标签打印机不同于小票打印机,前者用于打印临时标签,可以包含名称、价格、重量、条码等信息,贴于散装称重商品,需选购;后者打印小票给顾客提供购买商品的明细,一般收银机自带。


    附:几大收银软件功能对比(从商超的角度分析)注:部分功能可能因配置不同而不同,具体应咨询客服。


    二维火来钱快美团收银
    版本

    餐饮版

    商超版

    奶茶版:适用连锁店

    基础版:适用餐饮

    零售版:适用超市

    专业版:适用连锁餐厅

    操作系统安卓安卓安卓
    适配触摸屏
    扫描商品条码、支付二维码
    对接外卖平台美团外卖、饿了么、百度外卖美团外卖、饿了么
    美团外卖
    手机下单免费微店、客户自助点餐
    收银机点餐、顾客扫码点餐、服务员点餐
    收银方式聚合支付、支付宝、微信、现金现金、支付宝、微信、银联、优惠券组合付款现金、支付宝、微信、优惠券、会员、记账
    会员功能会员等级、积分、折扣、微信公众号营销会员等级、充值、积分、折扣、俏销短信、报表短信、储值、积分
    支持手机火掌柜App

    来钱快App 收银助手

    美团管理APP
    报表
    自制条码(接标签打印机)
    商超版、奶茶版支持
    盘点库存手机操作电脑端
    对接电子秤商超版支持
    连锁店管理专业版支持
    其它特点

    排队叫号;

    自主微信公众号粉丝运营;

    小程序点餐

    免费微信店铺,推广公众号

    提高美团和大众点评店铺人气;

    排队软件;

    一店多收银台;

    售价


    买收银机赠(零售 488 元),免费升级,连锁版每用户 599 元零售版和基础版免费(买收银机赠),专业版收费
    相关链接

    官方网站:http://www.2dfire.com/


    来钱快:

    http://www.lqksyj.com/

    杭州萨宝科技:

    http://www.91laiqian.cn/

    软件和驱动:

    http://www.sabove.com/

    官方网站:

    http://shouyin.meituan.com/

    视频教程:

    http://shouyin.meituan.com/productGuide


    我的评价界面拟物还不错,具体没有深究公司没有美团有名,但是入行比美团早,产品成熟,创新力强可能更适合于奶茶店类型,对打包、外卖完美适用,有机会试用的话可以给出更客观的评价

    本文资料整理于 2018 年 7 月


    舟山海鲜捕鱼人品牌诚招代理,电话:13646674565(微信同号)

    xoyozo 8 年前
    3,911