Reputation: 964
I am trying to make a function that counts changed, added, new, etc files in the local git repo. I am using git status -s
for this and it returns ?? somefile
if a file is untracked. The ??
part is the part I check to determine what type of entry it is.
However when checking with
if [ "${lPrefix}" == '??' ]; then ...
All changes uu, m, a, d
are counted as untracked.
How can I make sure that the check works as intended and only triggers on untracked files.
I have tried replacing ??
with:
"??"
-> did not work, guessing it is because ?
is a wildcard"\?\?"
-> did not work, expected it to work'\?\?'
-> did not expect it to work as '
means literalEDIT: As requested by Jack Bracken The lPrefix is set as follows(it is also all code where the problem lies)
while IFS='' read -r line || [[ -n "$line" ]]; do
arr=($line)
lPrefix="${arr[0]}"
if [ "${lPrefix}" == '??' ]; then lPrefix="N"; fi
...
done < <(git status -s)
Upvotes: 1
Views: 562
Reputation: 46853
You have two problems here:
?
),git
's terminology) status
.First, fix the second problem: use git status --porcelain
or, rather git status -z
(which implies --porcelain
), to have entries separated by a null byte (which makes parsing safe). We'll then parse using read
with the null delimiter: -d ''
To fix the first problem, I'll try to understand what you want to do: each line given by git status -s
(or, rather git status --porcelain
) starts with a code; if this code is ??
then set lPrefix
to N
, otherwise set lPrefix
to the code found.
This should then do:
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)
Why did your command fail?
It's difficult to tell exactly, but it's probably related to the antipattern you used to split the string:
arr=($line)
lPrefix="${arr[0]}"
This is very bad! it's a (sadly) very common antipattern, given by people who don't really understand (or overlook) how the shell performs word splitting and filename expansion: if the expansion $line
contains glob characters (i.e., *
, ?
, [...]
and extended globs if extglob
is on), then the shell will not only perform word splitting, but also perform filename expansion, that is, it will match every glob with found files.
In your case, if you happen to have filenames with 2 characters (e.g., a file named ab
) in the current directory, then ${arr[0]}
will be this file! Look:
$ 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?
Note also that I didn't use
while read state file; do ... ; done < <(git status -s)
or something similar (like in the accepted answer) to get the state and the file name, because that would trim leading and trailing newlines in both state
and file
.
Upvotes: 4
Reputation: 74685
Whenever scripting with the output of git
, it's best to use -z
to work with null-separators.
I would recommend changing your script to this:
git status -z | while read -r -d '' status file; do
[ "$status" = '??' ] && printf '%s\n' "$file"
done
As mentioned in the comments (thanks), if left unquoted, the ??
will expand to the name of any files with exactly two characters in their name. Adding quotes ensures that the question marks are treated as literal characters. Adding quotes results in consistent behaviour, whether using [
or the bash-specific [[
.
When you want simple string matching, my advice would be to use [
with =
(and don't forget to quote your variables, as always!).
I'm assuming that you plan on executing some shell command for every file with ??
status. Otherwise, remember that the shell isn't designed for text processing, so you should be using a tool such as awk/sed.
Upvotes: 2
Reputation: 158090
The ??
should not be a problem here. I guess your variable ${lPrefix}
isn't properly set. I tried the following code and it worked:
git status -s | while read state file ; do
[ "${state}" = '??' ] && echo "${state} new"
done
However using bash for this is not really efficient. I strongly recommend to use awk
like this:
git status -s | awk '{c[$1]++}END{for(i in c){print i, c[i]}}'
Explanation in multiline version:
#!/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]
}
}
Upvotes: 2
Reputation: 510
You can try :
cat 1.txt | grep -Po "(?<=\?\?\ ).*"
1.txt is:
?? aaa
!! bb
?? vv
~~ xx
Break-down:
grep -Po
Perl expression, show only matching lines
("?<="
Starts with
\?\?\
Escaped question marks and a space
).*
Anything coming after that initial match
Upvotes: -1