当从git diff --name-only迭代结果时,如何处理文件名中的空格

时间:2015-01-23 12:03:45

标签: git bash spaces git-bash git-diff

我正在处理的脚本需要从git diff中查看每个文件。但是,我不知道如何处理文件名中的空格。任何有空格的文件都会被分成" 2个文件"。我知道他们需要被" "包裹起来但我不知道如何在它进入@ param之前实现这一目标。

当文件名中有空格时,我应该如何迭代来自

的文件
git diff --name-only  $1

这是一个简单的测试,可以重现错误:

copyfiles()
{
    echo "Copying added files"
    for file in $@; do

        new_file=$(echo ${file##*/})

        directory=$(echo ${file%/*})
        echo "Full Path is is  $file"
        echo "File is  $new_file"
        echo "Directory is  $directory"
        cp $file $COPY_TO
    done    
}

COPY_TO="testDir"
DIFF_FILES=$( git diff --name-only  $1) 
copyfiles $DIFF_FILES 

该脚本目前运行如下:

test.sh <git commit id>

5 个答案:

答案 0 :(得分:2)

使用-z让git-diff使用null终止符。例如:

export COPY_TO
git diff -z --name-only | xargs -0 sh -c 'for file; do
    new_file=$(echo ${file##*/})
    directory=$(echo ${file%/*})
    echo "Full Path is is  $file"
    echo "File is  $new_file"
    echo "Directory is  $directory"
    cp "$file" "$COPY_TO"
done' sh

请注意,更合理的解决方案是拒绝来自创建名称中带有空格的文件的人的拉取请求。

答案 1 :(得分:2)

--name-only的输出受到一定数量的转义。不幸的是,与之合作很尴尬。

git diff解释了-z选项下的转义(和替代):

  

-z

     

当给出--raw, - numstat, - name-only或--name-status时,请不要使用NUL作为输出字段终止符。

     

如果没有此选项,每个路径名输出将分别用\ t,\ n,\“和\替换为TAB,LF,双引号和反斜杠字符,如果有的话,路径名将用双引号括起来那些替换发生了。

一个例子:

$ git init ugh
$ cd ugh
$ touch 'spa ce' $'new\nline' $'t\tab'
$ ls # Unhelpful really
new?line  spa ce  t?ab
$ ls --quote # Minorly helpful but wrong (for shell usage)
"new\nline"  "spa ce"  "t\tab"
$ git add -A
$ git diff --cached --name-only
"new\nline"
spa ce
"t\tab"
$ git diff --cached --name-only -z # Doesn't copy and paste well and is a bit confusing to read this way
new
line^@spa ce^@t ab^@
$ printf %q\\n "$(git diff --cached --name-only -z )"
$'new\nlinespa cet\tab'

无论如何,这里的要点是,最好的方法是使用-z输出并使用read读取文件列表。

while IFS= read -r -d '' file; do
    printf 'file = %q\n' "$file"
done < <(git diff --cached --name-only -z)

您也可以将git diff的输出传递给while循环,但是如果循环完成后需要来自循环内部的变量,则需要使用此过程替换方法来避免子shell问题。管道方法D.

答案 2 :(得分:0)

git diff -z --name-only |
while read -d $'\0' file
do
    echo ${file}
done

答案 3 :(得分:0)

感谢@Etan Resiner for your answer。这是一个示例,显示如何使用git diff --name-only -z "$merge_base" $BACKUP_BRANCH的输出作为输入来包含发送到git diffgit difftool中的转义文件名。它需要一个额外的--,因此请参见下面的代码。

我能够用它来修复my git changes program,所以现在它可以处理git repo中文件名中包含空格或特殊字符(例如')的文件名。现在,程序看起来像这样:

用法:

Usage: git changes <common_base> <backup_branch> [any other args to pass to git difftool]

git-changes.sh:

尤其要注意填充files_changed_escaped变量,该变量是直接从@Etan Reisner的答案中学到的。

COMMON_BASE_BRANCH="$1"
BACKUP_BRANCH="$2"
# Obtain all but the first args; see:
# https://stackoverflow.com/questions/9057387/process-all-arguments-except-the-first-one-in-a-bash-script/9057392#9057392
ARGS_3_AND_LATER="${@:3}"

merge_base="$(git merge-base $BACKUP_BRANCH $COMMON_BASE_BRANCH)"
files_changed="$(git diff --name-only "$merge_base" $BACKUP_BRANCH)"

echo "Checking for changes against backup branch \"$BACKUP_BRANCH\""
echo "only in these files which were previously-modified by that backup branch:"
echo "--- files originally changed by the backup branch: ---"
echo "$files_changed"
echo "------------------------------------------------------"
echo "Checking only these files for differences between your backup branch and your current branch."

# Now, escape the filenames so that they can be used even if they have spaces or special characters,
# such as single quotes (') in their filenames!
# See: https://stackoverflow.com/questions/28109520/how-to-cope-with-spaces-in-file-names-when-iterating-results-from-git-diff-nam/28109890#28109890
files_changed_escaped=""
while IFS= read -r -d '' file; do
    escaped_filename="$(printf "%q" "$file")"
    files_changed_escaped="${files_changed_escaped}    ${escaped_filename}"
done < <(git diff --name-only -z "$merge_base" $BACKUP_BRANCH)

# DEBUG PRINTS. COMMENT OUT WHEN DONE DEBUGGING.
echo "$files_changed_escaped"
echo "----------"
# print withOUT quotes to see if that changes things; ans: indeed, it does: this removes extra 
# spaces and I think will replace each true newline char (\n) with a single space as well 
echo $files_changed_escaped 
echo "=========="

# NB: the `--` is REQUIRED before listing all of the files to search in, or else escaped files
# that have a dash (-) in their filename confuse the `git diff` parser and the parser thinks they
# are options! It will output this error:
#       fatal: option '-\' must come before non-option arguments
# Putting the list of all escaped filenames to check AFTER the `--` forces the parser to know
# they cannot be options, because the `--` with nothing after it signifies the end of all optional
# args.
git difftool $ARGS_3_AND_LATER $BACKUP_BRANCH -- $files_changed_escaped
echo "Done."

您可以在https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles处下载git changes程序作为我的dotfiles项目的一部分。

它还包含诸如git diffn之类的东西,它是带有行号的git diff

答案 4 :(得分:0)

我认为你的代码需要这个命令 IFS=$'\n'

echo "this command is important"

IFS=$'\n'
for file_change in `git diff --name-only $1`
do
    echo "Put $file_change ..."

    # File Name
    fileName=$(basename "$file_change")
    echo "$fileName"

    # Directory
    dir=$(dirname "$file_change")
    echo "$dir"
    

    # copy file
    cp $file_change $REMOTE_DIR$file_change
done