Для быстрого и простого решения добавьте это в ваш 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-*
версий этих функций слова разделяются не -метасимволами оболочки в кавычках .
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"
^
Вот более эффективный способ сделать то, что вы пытаетесь сделать. Я использую меньший набор образцов, чтобы все было понятнее :
.#!/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
выводит различия на терминал. Это приводит к сбою&&
(И ), поэтому в файл-заполнитель ничего не выводится.
Если у вас есть дополнительные вопросы по синтаксису, не стесняйтесь спрашивать.
Кажущийся странным вывод вашего кода может быть объяснен тем фактом, что ваш сценарий всегда только добавляет в выходной файл. Это означает, что у вас могла быть некоторая ошибка в вашем коде ранее (, теперь исправленная ), но вы все еще видите вывод этого запуска в выходном файле, поскольку выходной файл никогда не удаляется и не очищается сценарием.
Вы можете сократить свой сценарий до
#!/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