likes
comments
collection
share

Git命令中的<pathspec>是什么?

作者站长头像
站长
· 阅读数 4

什么是pathspec

pathspec是git命令中的一个可选项,它可以用于限制git命令的作用范围,这个范围通常是指仓库中的子集(包括文件和文件夹)。git中的很多命令都可以带上该选项,比如说"git ls-files"、"git ls-tree"、"git add"、"git grep"、"git diff"和"git checkout"。 Git命令中的<pathspec>是什么?

使用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不能一起使用。否则就会报错: Git命令中的<pathspec>是什么?

总结

pathspec的作用就是用于限制git命令的作用域范围,但是可以使用通配符或者魔法签名等方式做到更加精细的控制,让git命令的作用域范围更加的灵活多变。

参考

marvinsblog.net/post/2019-1…

css-tricks.com/git-pathspe…

git-scm.com/docs/gitglo…