Как справиться с пробелами в именах файлов при повторении результатов из git diff --name-only

Сценарий, над которым я работаю, должен просмотреть каждый файл из git diff. Однако я не знаю, как бороться с пробелами в имени файла. Любые файлы, в которых есть пробел, делятся на «2 файла». Я знаю, что их нужно обернуть в " ", но я не знаю, как этого добиться, прежде чем это перейдет в параметр @.

Если в именах файлов есть пробелы, как мне перебирать файлы из

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>

person chrispepper1989    schedule 23.01.2015    source источник


Ответы (5)


Используйте -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

Обратите внимание, что более разумным решением является отказ от запросов на вытягивание от людей, которые создают файлы с пробелами в имени.

person William Pursell    schedule 23.01.2015
comment
люблю разумное решение, я не могу понять, почему исходные файлы заканчиваются пустым пространством! - person chrispepper1989; 23.01.2015
comment
Могу я спросить, почему просто добавление -z в мой текущий скрипт не сработало? - person chrispepper1989; 23.01.2015
comment
Я не могу понять, как использовать это так, чтобы я мог запускать функцию для каждого файла .. - person chrispepper1989; 23.01.2015
comment
Если вы используете bash, вы можете экспортировать функцию с помощью export -f, а затем выполнить xargs -0 -I {} bash -c 'function_name {}'. Это вызывает функцию один раз для каждого файла, а не передает несколько имен файлов. Однако я настоятельно не рекомендую этого делать, потому что экспорт функций - занудство. Вместо этого поместите его в сценарий оболочки. - person William Pursell; 23.01.2015
comment
Однако проще просто сделать xargs -0 bash -c 'function_name "$@"' bash. Это вызовет функцию с несколькими аргументами. Важно export -f function_name - person William Pursell; 23.01.2015
comment
Если я отправлю его как несколько аргументов, не будет ли у меня такой же проблемы с пробелами? Когда вы говорите поместить его в сценарий оболочки, вы имеете в виду отдельный сценарий, который я вызываю? На данный момент я просто скопировал и вставил содержимое функции в '', и он действительно работает, просто кажется неопрятным: p - person chrispepper1989; 25.01.2015
comment
Использование нескольких аргументов является проблемой только в том случае, если ваша функция использует for file in $@ вместо for file; do или for file in "$@";do - person William Pursell; 26.01.2015
comment
О, как странно, всегда лучше использовать делать то? - person chrispepper1989; 26.01.2015
comment
Do - это обязательный синтаксис для цикла for. Важное различие - двойные кавычки вокруг $ @. Я упоминаю do во втором примере, чтобы пояснить, что for file; do идентичен for file in "$@"; do, который ведет себя совсем не так, как for file in $@; do. - person William Pursell; 26.01.2015

Выходные данные --name-only подлежат определенному экранированию. К сожалению, работать с ним неудобно.

git diff объясняет экранирование (и альтернативу) с помощью параметра -z:

-z

Когда заданы --raw, --numstat, --name-only или --name-status, не меняйте пути и используйте NUL в качестве ограничителей выходных полей.

Без этого параметра в каждом выходном имени пути символы TAB, LF, двойные кавычки и обратная косая черта будут заменены на \ t, \ n, \ "и \ соответственно, а имя пути будет заключено в двойные кавычки, если произошла какая-либо из этих замен. .

Пример:

$ 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, но если вам нужны переменные внутри цикла после завершения цикла, вам понадобится этот метод подстановки процесса, чтобы избежать проблем подоболочки с методом канала D.

person Etan Reisner    schedule 23.01.2015
comment
Этот ответ был очень полезным. Спасибо. Это позволило мне передать outpuf из git diff --name-only -z как вход в _2 _ / _ 3_. Я демонстрирую это здесь: stackoverflow.com/a/62853776/4561887 - person Gabriel Staples; 11.07.2020

Спасибо, @Etan Resiner за ваш ответ. Вот пример, показывающий, как использовать вывод git diff --name-only -z "$merge_base" $BACKUP_BRANCH в качестве ввода, чтобы содержать экранированные имена файлов, отправленные в git diff или git difftool. Для этого требуется дополнительный --, поэтому см. Код ниже.

С его помощью мне удалось исправить мою git changes программу , поэтому теперь он может обрабатывать имена файлов в репозитории 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="$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."

Вы можете загрузить программу git changes как часть моего проекта dotfiles здесь: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles / а>.

Он также содержит такие вещи, как git diffn, то есть git diff с номерами строк.

person Gabriel Staples    schedule 11.07.2020

Думаю, вашему коду нужна эта команда 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
person Amir Hosseinzadeh    schedule 16.06.2021

person    schedule
comment
Будьте осторожны: read -d работает только в bash, но не в sh. - person moi; 06.10.2020