Скрипт некорректно печатает правильные элементы массива

Для быстрого и простого решения добавьте это в ваш inputrc (выберите подходящие ключи для себя):

"\e\C-b": shell-backward-kill-word
"\eh": shell-backward-word
"\e\C-f": shell-forward-word
# Swap the preceding two arguments (control + alt + t)
"\e\C-t": "\e\C-b\eh\C-y"
# Swap the preceding argument with the next (control + alt + p)
"\e\C-p": "\e\C-b\e\C-f\C-y"

В случае shell-*версий этих функций слова разделяются не -метасимволами оболочки в кавычках .

metacharacter

A character that, when unquoted, separates words. A metacharacter is a space, tab, newline, or one of the following characters: ‘|’, ‘&’, ‘;’, ‘(’, ‘)’, ‘<’, or ‘>’.

Примечание :Курсор должен быть после второго аргумента перед нажатием Ctrl + Alt + t , поэтому он эффективно помещает аргумент перед курсор к началу строки.

$ true foo/bar.xyz even/without\ quotes.ok "too/too far.away"
                                                             ^
$ true foo/bar.xyz "too/too far.away" even/without\ quotes.ok
                                     ^
$ true "too/too far.away" foo/bar.xyz even/without\ quotes.ok
                         ^

Примечание :Курсор должен быть после первого аргумента перед нажатием Ctrl + Alt + p , поэтому он эффективно вытягивает аргумент перед курсор в конец строки.

$ true "too/too far.away" foo/bar.xyz even/without\ quotes.ok
                         ^
$ true foo/bar.xyz "too/too far.away" even/without\ quotes.ok
                                     ^
$ true foo/bar.xyz even/without\ quotes.ok "too/too far.away"
                                                             ^

-2
17.03.2019, 20:06
2 ответа

Вот более эффективный способ сделать то, что вы пытаетесь сделать. Я использую меньший набор образцов, чтобы все было понятнее :

.
#!/bin/bash

# clear placeholder
printf "Files with no diff:\n" > placeholder

# set up sample data
echo "one" > file.00.txt
echo "one" > file.01.txt
echo "foo" > file.02.txt
echo "bar" > file.03.txt
echo "two" > file.04.txt
echo "two" > file.05.txt 

# generate array
i=0
while [ $i -lt 6 ]; do    
    array+=( file.`printf %02d $i`.txt )
    ((i++))
done

i=0
while [ $i -lt 5 ]; do
    diff --brief ${array[i]} ${array[i+1]} && \
    echo "${array[i]} ${array[i+1]}" >> placeholder 
    ((i++))
done

Результаты:

$ sh./test.sh 
Files file.01.txt and file.02.txt differ
Files file.02.txt and file.03.txt differ
Files file.03.txt and file.04.txt differ
$ cat placeholder 
Files with no diff:
file.00.txt file.01.txt
file.04.txt file.05.txt

Вам не нужно генерировать образцы данных, если у вас уже есть данные.

Объяснение кода:

Построение массива в цикле (в Bash )может быть выполнено путем итерации, как вы, очевидно, уже знаете, но нотация array+=добавляет элемент.

Очевидно, ((++))увеличивает ваш счетчик.

Выполняя diff, я использую опцию --brief. Если вы читаете справочную страницу diff, там сказано, что --briefвыводит вывод только при обнаружении различий. Следовательно, команда diff завершается успешно , если diff не найден.

Используя нотацию&&(И ), этот код повторяет имена сравниваемых файлов в вашем файле placeholderтогда и только тогда, когда команда diffне генерирует выходных данных.

Если есть разница между файлами , diffвыводит различия на терминал. Это приводит к сбою&&(И ), поэтому в файл-заполнитель ничего не выводится.

Если у вас есть дополнительные вопросы по синтаксису, не стесняйтесь спрашивать.

0
28.01.2020, 05:16

Кажущийся странным вывод вашего кода может быть объяснен тем фактом, что ваш сценарий всегда только добавляет в выходной файл. Это означает, что у вас могла быть некоторая ошибка в вашем коде ранее (, теперь исправленная ), но вы все еще видите вывод этого запуска в выходном файле, поскольку выходной файл никогда не удаляется и не очищается сценарием.


Вы можете сократить свой сценарий до

#!/bin/bash

array=( file.*.txt )

for name in "${array[@]}"; do
    if [ -n "$prev_name" ] && cmp -s "$prev_name" "$name"
    then
        printf '%s y %s\n' "$prev_name" "$name"
    fi

    prev_name=$name
done

При этом используется шаблон подстановки для заполнения массива именами файлов, соответствующими шаблону.

Затем он перебирает имена, сравнивая имена файлов, которые встречаются рядом друг с другом в массиве, используя cmp -s. Утилита cmpзавершится со статусом выхода true , если содержимое двух файлов, которые она сравнивает, идентично.

Цикл использует $prev_nameдля хранения имени предыдущего файла в массиве. В первой итерации цикла эта переменная пуста, поэтому фактическое сравнение файлов пропускается.


То, что вы, возможно, ожидаете , напишет двойной цикл. Что-то вроде

for nameA in "${array[@]}"; do
    for nameB in "${array[@]}"; do
        if [ "$nameA" != "$nameB" ] && cmp -s "$nameA" "$nameB"
        then
            printf '%s y %s\n' "$nameA" "$nameB"
        fi
    done
done

Но это сравнило бы AсBиBс A, и количество вызовов cmpвыросло бы квадратично с количеством задействованных файлов, что потребовало бы больших ресурсов (. ] на дисках; он будет читать каждый файл столько раз, сколько имен файлов в массиве ), и будет работать медленно.


Обычный способ поиска наборов файлов с идентичным содержимым —fdupes:

$ fdupes --sameline.
./file.1.txt./file.2.txt./file.7.txt

Хотели бы вы сделать что-то подобное без fdupes, вы могли бы сделать это, вычислив и сравнив контрольную сумму каждого файла, используя, например,.md5sum:

#!/bin/bash

declare -A names count

while read -r cksum name; do
    names[$cksum]+=${names[$cksum]:+,}$name
    count[$cksum]=$(( count[$cksum] + 1 ))
done < <( md5sum file.*.txt )

for cksum in "${!count[@]}"; do
    if [ "${count[$cksum]}" -gt 1 ]; then
        printf '%s\n' "${names[$cksum]}"
    fi
done

Первый цикл считывает вывод md5sum, который выполняется для всех соответствующих файлов. Вывод md5sumможет выглядеть примерно так

897316929176464ebc9ad085f31e7284  file.1.txt
8c9eb686bf3eb5bd83d9373eadf6504b  file.10.txt
897316929176464ebc9ad085f31e7284  file.2.txt
26ab0db90d72e28ad0ba1e22ee510510  file.3.txt
84bc3da1b3e33a18e8d5e1bdd7a18d7a  file.4.txt
aa6ed9e0f26a6eba784aae8267df1951  file.5.txt
6d7fce9fee471194aa8b5b6e47267f03  file.6.txt
897316929176464ebc9ad085f31e7284  file.7.txt
c30f7472766d25af1dc80b3ffc9a58c7  file.8.txt
9ae0ea9e3c9c6e1b9b6252c8395efdc1  file.9.txt

Контрольная сумма в первом столбце считывается в cksum, а имя файла считывается в name.

Внутри первого цикла мы добавляем имя к записи в ассоциативном массиве, который индексируется по контрольной сумме. То, как выполняется присваивание names[$cksum], гарантирует, что мы добавляем запятую перед каждым новым именем, если это необходимо (, что происходит, если запись уже содержит некоторые другие имена ). Затем мы обновляем количество раз, когда мы видели эту конкретную контрольную сумму (, она будет использоваться во втором цикле ).

Во втором цикле проходим по контрольным суммам("${!count[@]}"расширяем до списка ключей (контрольных сумм )в ассоциативном массиве count)и для каждой контрольной суммы проверяем, соответствует ли ее счет больше 1, что означает, что мы нашли дубликат файла (. Если вы ищете группы ровно из трех одинаковых файлов, вы можете использовать -eq 3вместо -gt 1здесь ). Если это так, мы печатаем имена, связанные с этой контрольной суммой.

Проверка:

$ bash script.sh
file.1.txt,file.2.txt,file.7.txt
1
28.01.2020, 05:16

Теги

Похожие вопросы