什么是pathspec
pathspec是git命令中的一个可选项,它可以用于限制git命令的作用范围,这个范围通常是指仓库中的子集(包括文件和文件夹)。git中的很多命令都可以带上该选项,比如说"git ls-files"、"git ls-tree"、"git add"、"git grep"、"git diff"和"git checkout"。
使用pathspec
pathspec可以写成多种格式去匹配git仓库中的文件。
方式一:绝对路径或相对路径
pathspec最简单的用法就是直接写成文件路径字符串。举个例子,假设项目中有如下结构:
└─ fruits.txt
└─ books.js
└─ src
└─ clothes.txt
└─ pants.js
如果此时想查看git项目中的文件就可以使用git ls-files加上pathspec的方式:
git ls-files .
### 控制台输出结果
books.js
fruits.txt
src/clothes.txt
src/pants.js
git ls-files fruits.txt
### 控制台输出
furits.txt
git ls-files ./src/clothes.txt
#### 控制台输出
src/clothes.txt
git ls-files src
### 控制台输出
src/clothes.txt
src/pants.js
上述几个命令中的git ls-files后跟的路径就是pathspec,可以是相对路径或绝对路径。
甚至还可以使用多个pathspec来查看多个文件:
git ls-files fruits.txt ./src
### 控制台输出结果
fruits.txt
src/clothes.txt
src/pants.js
方法二:使用通配符
pathspec还可以使用通配符的这种模式,通配符有"*","?"和"[]"三种符号。需要注意的是:"*"通配符还会在子文件夹中进行文件匹配,其他两种通配符则不会。
*通配符
"*"通配符的作用是匹配尽可能多个任意字符。举个例子,假设项目中有如下结构:
└─ fruits.txt
└─ books.js
└─ src
└─ clothes.txt
└─ pants.js
此时,就可以使用"*"通配符匹配项目中的所有带"txt"后缀的文件:
git ls-files '*.txt'
### 控制台输出
fruits.txt
src/clothes.txt
可以看到,控制台输出了根目录的fruits.txt和src文件夹下的clothes.txt。
注意!注意!注意!重点来了。在使用"*"通配符作为pathspec的时候,如果想同时匹配子集文件夹中的文件,则需要在外面加上引号。因为加引号和不加引号会被git作为两种不同的方式进行处理。
在使用pathspec的时候没有加上引号的情况下,会默认使用shell查找文件的方式。比如:
git ls-files *.txt
### 控制台输出
fruits.txt
上面结果只输出了根目录的fruits.txt,并没有输出src文件夹下的clothes.txt文件,这和直接使用shell输出文件相同:
ls *.txt
### 控制台输出
fruits.txt
两种方式都只会输出根目录匹配到的fruits.txt文件。
如果在pathspec外面加上了引号,那么引号内的内容则会以fnmatch(3)的方式去解析。同时,pathspec也包含"*"通配符的情况下,则还会在子文件夹中继续寻找文件。建议在使用通配符的情况下都带上引号。
以下列举几种常见的使用"*"通配符的使用场景:
git ls-files '*.*' // 匹配所有文件,包含子文件夹中的文件
### 控制台输出
books.js
fruits.txt
src/clothes.txt
src/pants.js
git ls-files 'src/*.*' // 只包含src下的所有文件
### 控制台输出
src/clothes.txt
src/pants.js
git ls-files '*/*.js' // 匹配子目录下的js文件,如果src下还有子目录且包含js文件,也会被打印
### 控制台输出
src/pants.js
?通配符
在pathspec中使用"?"通配符,可以匹配任意单个字符。同样看例子,假设项目中有如下结构:
└─ books.js
└─ books.ts
└─ src
└─ books.js
└─ books.ts
这时候如果要想找到文件名是books且后缀名是.ts和.js的文件,就可以使用?去匹配,操作如下:
git ls-files 'books.?s' // 匹配books.js和books.ts文件
### 控制台输出
books.js
books.ts
结果如上,在控制台输出了根目录下的books.js和books.ts,但是在src下的相同名称的文件并没有被匹配到。说明"?"通配符并不会在子目录文件夹中查找。这一点是和"*"通配符是不一样的。
[]通配符
[]通配符和?的使用方式比较相似,同样是匹配单个字符,不过它的匹配字符只能在一个给定的集合中筛选。
假设项目中有如下结构:
└─ books.js
└─ books.ts
└─ books.ps
└─ src
└─ books.js
└─ books.ts
└─ books.ps
如果要想找到文件名是books且后缀名是.ts和.js的文件,就不能使用"?"了因为会匹配到.ps结尾的文件,这时候就可以使用[]通配符,操作如下:
git ls-files 'books.[jt]s' // 匹配books.js和books.ts文件
### 控制台输出
books.js
books.ts
结果如上,成功匹配到了books.js和books.ts文件并且没有匹配到books.ps文件。和"?"通配符相同也不会在子文件夹中继续查找。
[]通配符还有一些比较有趣的用法,比如明确要匹配的单个字符是0-9的数字,就可以使用类似正则表达式中的\d这种预设模式。来看看具体用法,假设项目中有如下结构:
└─ books.1.js
└─ books.2.ts
└─ books.3.ps
└─ books.4.js
...
└─ books.9.js
现在需要查找所有文件名为books的文件,操作如下:
git ls-files 'books.[[:digit:]].[jtp]s'
### 控制台输出
books.1.js
books.2.ts
books.3.ps
books.4.js
...
books.9.js
结果如上,在控制台成功打印了想要匹配的文件。需要注意的是,使用该操作是需要两个中括号进行包裹的。除了数字模式外,还有其他的预设匹配模式,具体可以参考手册。
方法三:使用魔法签名
魔法签名是一种更复杂且更加灵活的匹配方式,使用方式是在pathspect的开头加上:(signature)
。(签名)signature有以下几种:top、icase、litera、glob、attr和exclude。
top
top签名可以让git命令总是从根目录开始执行,不管当前是在哪层目录下。
假如现在是在嵌套很深的一层目录下,并且想获知根目录有什么文件,这时候就可以使用top签名让ls-files从根目录输出文件,这样就不用再返回根目录。
接下去模拟一下上述场景,假设项目中有如下结构:
└─ books.txt
└─ fruits.txt
└─ src
└─ pants.txt
└─ folder ------ 此时在该目录下
└─clothes.txt
此时,文件操作是在src/folder文件夹下,如果想获取根目录的所有txt文件则可以这样操作:
git ls-files ':(top)*.txt'
### 控制台输出
books.txt
fruits.txt
git ls-files ':/*.txt' // 这是一种缩写格式
### 控制台输出
books.txt
fruits.txt
可以看到,在控制台成功输出了根目录的所有"txt"文件,而不是当前目录下的clothes.txt文件。另外,还可以将:(top)
进行简写使用:/
的方式。
icase
icase的作用是用于忽略pathspec中的大小写。比如用于匹配图片时,jpg和JPG表示的是同一种文件格式,就可以使用icase的进行匹配。
假设项目中有如下结构:
└─ pig.jpg
└─ dog.JPG
现在需要匹配所有的jpg图片,可以使用如下操作:
git ls-files ':(icase)*.jpg'
### 控制台输出
pig.jpg
dog.JPG
这样就能忽略大小写,成功匹配到两张图片。
literal
literal可以将某些具有特殊意义的字符作为真正的字面量字符串去匹配,比如说将"*"和"?"作为字符串而不是作为通配符。
假设项目中有如下结构:
└─ *.txt
└─ fruits.txt
如果直接使用"*.txt"进行匹配的话会输出*.txt文件和fruits.txt文件。但是现在只想匹配*.txt文件该怎么办?很简单,就是需要将"*"通配符进行转义,操作如下:
git ls-files ':(literal)*.txt'
#控制台输出
*.txt
如上结果中,"*.txt"文件被成功匹配到了,正是因为直接将*转成了字面量字符串进行匹配,所以可以成功。
glob
glob魔法签名的作用就是在匹配文件的时候从当前目录
出发。格式是在开头加上:(glob)
,然后在pathspec中使用**
。
glob魔法签名的第一种用法就是加在路径的最前面。假设项目中有如下目录结构:
└─ fruits.txt
└─ src
└─ books.txt
└─ folder
└─ fruits.txt
如果想匹配项目中所有的fruits.txt文件(第一层目录和第三层目录),可以用"*"通配符去实现么?尝试一下"*/fruits.txt"匹配的结果:
git ls-files '*/fruits.txt'
### 控制台输出
src/foler/fruits.txt
结果只输出了src/foler目录中的fruits.txt文件,在根目录中的fruits.txt文件并没有被匹配到。为了解决这种情况,就可以使用glob签名,让匹配的目录包含当前目录:
git ls-files ':(glob)**/fruits.txt'
### 控制台输出
fruits.txt
src/folder/fruits.txt
如上结果,在根目录和子目录的"fruits.txt"文件都成功被匹配到了。
glob签名还可以加在pathspec的中间,用它来表示匹配0个或多个文件目录。假设项目中有如下结构:
└─ index.html
└─ page.html
└─ src
└─ index.html
└─ page.html
└─ header
└─ index.html
└─ page.html
└─ logo
└─ index.html
└─ page.html
每一层目录下都有一个index.html和page.html,此时的目标是只想输出src下的所有index.html文件,包括子文件夹中的。如果只使用"*"的话是匹配不到src目录下的index.html文件的:
git ls-files 'src/*/index.html'
### 控制台输出
src/header/index.html
src/header/logo/index.html
结果上看,"src/index.html"文件并没有被匹配到。解决办法就是在pathspec中使用glob
签名去匹配:
git ls-files ':(glob)src/**/index.html'
### 控制台输出
src/index.html
src/header/index.html
src/header/logo/index.html
glob
签名还可以加在路径的末尾,表示匹配该路径下的所有文件,使用效果和"*"差不多。假设项目中有如下结构:
└─ index.html
└─ src
└─ index.html
└─ header
└─ index.html
└─ logo
└─ index.html
现在需要匹配src下的所有index.html文件:
git ls-files 'src/*'
### 控制台输出
src/index.html
src/header/index.html
src/header/logo/index.html
git ls-files ':(glob)src/**'
### 控制台输出
src/index.html
src/header/index.html
src/header/logo/index.html
attr
attr签名通常需要配合.gitattributes文件一起使用。它可以将一些比较常用的路径用一个属性名去代替。
假设项目中有如下结构:
└─ index.html
└─ src
└─ index.html
└─ page.html
└─ header
└─ index.html
└─ page.html
└─ logo
└─ index.html
└─ page.html
假如其中的logo文件夹是一个比较常用目录,如果每次使用都要输入src/header/logo然后才能定位到,还是比较繁琐的。这时候就可以将其添加到.gitattributes文件中,并为它设置快捷属性名:
src/header/logo/* logo
然后就可以在命令中配合attr签名使用:
git ls-files ':(attr:logo)'
### 控制台输出
src/header/logo/index.html
src/header/logo/page.html
不止如此,还可以使用attr进行取反,也就是匹配不包括快捷属性的所有文件:
git ls-files ':(attr:!logo)'
### 控制台输出
index.html
src/index.html
src/page.html
src/header/index.html
src/header/page.html
.gitattributes文件的其他使用方法可以参考git的官网。
exclude
exclude这个签名的作用是匹配除了指定路径文件以外的所有文件。该签名具有缩写格式(:!或:^)。
假设项目中有如下结构:
└─ index.html
└─ src
└─ index.html
└─ header
└─ index.html
└─ logo
└─ index.html
假如想匹配除了src/header/logo下的所有文件,那么就可以使用如下方法:
git ls-files ':(exclude)src/header/logo/*'
// 或
git ls-files ':!src/header/logo/*'
// 或
git ls-files ':^src/header/logo/*'
#### 控制台输出
index.html
src/index.html
src/header/index.html
组合使用魔法签名
在使用pathspec时可以将多个魔法签名写在一起,然后使用","去分隔。
假设项目中有如下结构:
└─ index.html
└─ src
└─ index.html
└─ header
└─ index.html
└─ logo
└─ index.html
└─ avatar.JPG
如果想匹配除了src/header/logo/avatar.JPG以外的所有文件:
git ls-files ':!(icase,glob,exclude)src/**/*.jpg'
### 控制台输出
index.html
src/index.html
src/header/index.html
src/header/logo/index.html
但是需要注意的是glob和literal不能一起使用。否则就会报错:
总结
pathspec的作用就是用于限制git命令的作用域范围,但是可以使用通配符或者魔法签名等方式做到更加精细的控制,让git命令的作用域范围更加的灵活多变。