在if语句中转义双重问号

时间:2016-10-27 12:44:13

标签: bash

我正在尝试创建一个计算本地git仓库中已更改,添加,新等文件的函数。我正在使用git status -s,如果文件未跟踪,则返回?? somefile??部分是我检查以确定它是什么类型的条目的部分。 但是在使用

进行检查时
if [ "${lPrefix}" == '??' ]; then ...

所有更改uu, m, a, d都被视为未跟踪。

如何确保检查按预期工作,并且仅在未跟踪文件上触发。

我尝试将??替换为:

  • "??" - >没有用,猜测是因为?是一个通配符
  • "\?\?" - >没用,预计它会起作用
  • '\?\?' - >不希望它起作用,因为'意味着文字

编辑:按照Jack Bracken的要求 lPrefix设置如下(它也是问题所在的所有代码)

while IFS='' read -r line || [[ -n "$line" ]]; do
    arr=($line)
    lPrefix="${arr[0]}"
    if [ "${lPrefix}" == '??' ]; then lPrefix="N"; fi
    ...
done < <(git status -s)

4 个答案:

答案 0 :(得分:4)

这里有两个问题:

  1. 你危险地使用反模式来分割字符串(这里它实际上是一个危险的举动,因为你的字符串 包含了字符字符?) ,
  2. 您正在解析高级命令(在git的术语中称为瓷器命令)status
  3. 首先,解决第二个问题:使用git status --porcelain或更确切地说git status -z(这意味着--porcelain),使条目以空字节分隔(这使得解析安全)。然后我们将使用read和空分隔符-d ''

    进行解析

    要解决第一个问题,我会尝试了解您要执行的操作:git status -s(或者更确切地说git status --porcelain)给出的每一行都以代码开头;如果此代码为??,则将lPrefix设置为N,否则将lPrefix设置为找到的代码。

    然后应该这样做:

    while IFS= read -r -d '' line; do
        lPrefix=${line:0:2}
        filename=${line:3}
        if [[ $lPrefix = '??' ]]; then lPrefix=N; fi
        # other stuff...
    done < <(git status -z)
    

    为什么你的命令失败了?

    很难确切地说出来,但它可能与您用来分割字符串的反模式有关:

    arr=($line)
    lPrefix="${arr[0]}"
    

    这非常糟糕!它是一个(可悲的)非常常见的反模式,由那些不太了解(或忽略)shell如何执行分词和文件名扩展的人给出:如果扩展$line包含glob字符(即* },?[...]和扩展的全局如果extglob打开),那么shell不仅会执行分词,还会执行文件名扩展,也就是说,它会匹配每个glob找到文件。

    在您的情况下,如果您碰巧在当前目录中有2个字符的文件名(例如,名为ab的文件),则${arr[0]}将成为此文件!看:

    $ mkdir test
    $ git init
    $ touch a ab abc xy
    $ ls
    a  ab  abc xy
    $ git status -s
    ?? a
    ?? ab
    ?? abc
    ?? xy
    $ while IFS='' read -r line; do arr=($line); declare -p arr; done < <(git status -s)
    declare -a arr='([0]="ab" [1]="xy" [2]="a")'
    declare -a arr='([0]="ab" [1]="xy" [2]="ab")'
    declare -a arr='([0]="ab" [1]="xy" [2]="abc")'
    declare -a arr='([0]="ab" [1]="xy" [2]="xy")'
    $ # see the filename expansion?
    

    另请注意,我没有使用

    while read state file; do ... ; done < <(git status -s)
    

    或类似的东西(如在接受的答案中)获取状态和文件名,因为这会修剪statefile中的前导和尾随换行符。

答案 1 :(得分:2)

这里??应该不是问题。我猜您的变量${lPrefix}未正确设置。我尝试了以下代码并且它有效:

git status -s | while read state file ; do
    [ "${state}" = '??' ] && echo "${state} new"
done

然而,使用bash并不是很有效。我强烈建议您使用awk,如下所示:

git status -s | awk '{c[$1]++}END{for(i in c){print i, c[i]}}'

多行版本中的说明:

#!/usr/bin/awk -f

# c(ount) is an associative array, indexed by the values of the first column
# like '??', 'u', 'm' and so on. Each time they appear we
# increment their count by 1
{ c[$1]++ }

# Once the end of input has been reached we iterate trough
# c and print each index along with it's count.
END {
    for(i in c) {
        print i, c[i]
    }
}

答案 2 :(得分:2)

每当使用git的输出编写脚本时,最好使用-z来处理空分隔符。

我建议您将脚本更改为:

git status -z | while read -r -d '' status file; do 
    [ "$status" = '??' ] && printf '%s\n' "$file"
done

如评论中所述(感谢),如果不加引号,??将扩展为名称中只有两个字符的任何文件的名称。添加引号可确保将问号视为文字字符。无论是使用[还是使用特定于bash的[[,添加引号都会产生一致的行为。

当你想要简单的字符串匹配时,我的建议是将[=一起使用(并且不要忘记引用变量,一如既往!)。

我假设您计划为每个??状态的文件执行一些shell命令。否则,请记住shell不是为文本处理而设计的,因此您应该使用awk / sed等工具。

答案 3 :(得分:-1)

您可以尝试:

cat 1.txt | grep -Po "(?<=\?\?\ ).*"

1.txt是:

?? aaa
!! bb
?? vv
~~ xx

击穿:

grep -Po Perl表达式,仅显示匹配的行
("?<="从中开始 \?\?\转义问号和空格
).*在初始匹配后发生的任何事情