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

How to cope with spaces in file names when iterating results from git diff --name-only

我正在处理的脚本需要通过 git diff 遍历每个文件。但是,我不知道如何处理文件名中的spaces。任何具有 space 的文件都将拆分为“2 个文件”。我知道它们需要包装在 " " 中,但我不知道如何在进入 @ 参数之前实现这一点。

当文件名中有space时,如何遍历

中的文件
git diff --name-only  

?

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

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  ) 
copyfiles $DIFF_FILES 

该脚本目前 运行 如:

test.sh <git commit id>

使用 -z 得到 git-diff 以使用空终止符。例如:

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

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

--name-only 的输出需要进行一定程度的转义。不幸的是,使用起来很尴尬。

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

-z

When --raw, --numstat, --name-only or --name-status has been given, do not munge pathnames and use NULs as output field terminators.

Without this option, each pathname output will have TAB, LF, double quotes, and backslash characters replaced with \t, \n, \", and \, respectively, and the pathname will be enclosed in double quotes if any of those replacements occurred.

一个例子:

$ 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 循环,但是如果循环完成后您需要循环内部的变量,则需要此 Process Substitution 方法来避免子 shell 问题用管道方法 D.

git diff -z --name-only |
while read -d $'[=10=]' file
do
    echo ${file}
done

谢谢。这是一个示例,显示如何使用 git diff --name-only -z "$merge_base" $BACKUP_BRANCH 的输出作为输入来包含发送到 git diffgit difftool 的转义文件名。它需要一个额外的--,所以请看下面的代码。

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

用法:

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=""
BACKUP_BRANCH=""
# Obtain all but the first args; see:
# 
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: 
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."

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

里面还有git diffn之类的东西,也就是git diff加上行号。

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

echo "this command is important"

IFS=$'\n'
for file_change in `git diff --name-only `
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