Bezdepbonusu7Программирование: статьи и ответы

Вопрос

Цикл BASH while для проверки строк в файле отображается слишком много раз

Автор вопроса: zacgalfОтветов: 6

Я пишу скрипт, в котором хочу взять каждую строку из файла и проверить совпадение в другом файле. Если я найду совпадение, я хочу сказать, что я нашел совпадение, а если нет, то сказать, что я не нашел совпадения.

2 файла содержат хэши md5. Старый файл является исходным, а новый файл предназначен для проверки наличия каких-либо изменений по сравнению с исходным файлом.

исходный файл: chksum новый файл: chksum1

#!/bin/bash

while read e; do
     while read f; do
     if [[ $e = $f ]]
     then
     echo $e "is the same"
     else
          if [[ $e != $f]]
          then
          echo $e "has been changed"
          fi
     fi
     done < chksum1
done < chksum

Моя проблема в том, что для файлов, которые были изменены, я получаю эхо каждый раз, когда выполняется проверка в цикле, и я хочу, чтобы он отображал файл только один раз и говорил, что он не найден.

Надеюсь, это понятно.

  • Можете ли вы опубликовать образец из вашего файла? ИМО awk может сделать это лучше. jaypal singh 14.06.2013
  • Почему вы не хотите использовать diff? diff chksum chksum1 сделает именно то, что вы хотите. Nikolai Popov 14.06.2013
  • Небольшое дополнение: В части else не нужно проверять, отличаются ли значения, потому что они точно есть. Просто повторите строку. TrueY 14.06.2013
  • +1! Спасибо за вопрос! Я снова чему-то научился. Я прочитал man bash (снова) и обнаружил, что ${var:+val} и ${var+val} ведут себя по-разному! TrueY 14.06.2013
  • Я пытался использовать diff, но мне было трудно, так как я должен сказать, какие файлы были отредактированы, а какие нет. diff показал мне все файлы и после перебора вариантов я решил использовать цикл zacgalf 14.06.2013
  • @someguy Вы правы. Сохранение простоты не похоже на карты здесь. Я разместил решение с sort и uniq, но diff работает так же хорошо, если не лучше. Samveen 14.06.2013

bashloopswhile-loopecho

Ответы

Ответ 1 · Franko

вы можете использовать тот же сценарий, но поставить напоминание.

#!/bin/bash

while read e; do
    rem=0
        while read f; do
        if [[ $e = $f ]]
        then
            rem=1
        fi
        done < chksum1
    if [[ rem = 1 ]]
    then
        echo $e "is the same"
    else
        echo $e "has been changed"
    fi
done < chksum

Это должно работать правильно

Ответ 2 · cforbish

Вы были очень близки. Это будет работать:

while read e; do
     while read f; do
     found=0
     if [[ $e = $f ]]
     then
         # echo $e "is the same"
         found=1
         break
     fi
     done < chksum1
     if [ $found -ne 0 ]
     then
        echo "$e is the the same"
     else
        echo "$e has been changed"
     fi
done < chksum

Ответ 3 · TrueY

Немного упрощенная версия, позволяющая избежать многократного чтения одного и того же файла (bash 4.0 и выше). Я предполагаю, что файлы содержат уникальные имена файлов, а формат файла является результатом md5sum команда.

#!/bin/bash

declare -A hash
while read md5 file; do hash[$file]=$md5; done <chksum
while read md5 file; do
  [ -z "${hash[$file]}" ] && echo "$file new file" && continue
  [ ${hash[$file]} == $md5 ] && echo "$file is same" && continue
  echo "$file has been changed"
done <chksum1

Этот скрипт считывает первый файл в ассоциативный массив с именем hash. Индекс - это имя файла, а значение - контрольная сумма MD5. Второй цикл считывает второй файл контрольной суммы; имени файла нет в hash, он печатает file new file; если он находится в hash и значение равно, то это один и тот же файл; если не равно пишет file has been changed.

Входные файлы:

$ cat chksum
eed0fc0313f790cec0695914f1847bca  ./a.txt
9ee9e1fffbb3c16357bf80c6f7a27574  ./b.txt
a91a408e113adce865cba3c580add827  ./c.txt
$ cat chksum1
eed0fc0313f790cec0695914f1847bca  ./a.txt
8ee9e1fffbb3c16357bf80c6f7a27574  ./b.txt
a91a408e113adce865cba3c580add827  ./d.txt

Выход:

./a.txt is same
./b.txt has been changed
./d.txt new file

РАСШИРЕННАЯ ВЕРСИЯ

Также обнаруживать удаленные файлы.

#!/bin/bash

declare -A hash
while read md5 file; do hash[$file]=$md5; done <chksum
while read md5 file; do
  [ -z "${hash[$file]}" ] && echo "$file new file" && continue
  if [ ${hash[$file]} == $md5 ]; then echo "$file is same"
  else echo "$file has been changed"
  fi
  unset hash[$file]
done <chksum1
for file in ${!hash[*]};{ echo "$file deleted file";}

Выход:

./a.txt is same
./b.txt has been changed
./d.txt new file
./c.txt deleted file

Ответ 4 · Samveen

Я хотел бы предложить альтернативное решение: как насчет того, чтобы вы не читали построчно, а использовали sort и uniq -c, чтобы увидеть, есть ли различия. Нет необходимости в петле, где простая труба может сделать вашу работу.

В этом случае вам нужны все строки, которые изменились в файле chksum1, поэтому

sort chksum chksum1 chksum1 | uniq -c | egrep '^\s+2\s' | sed 's%\s\+2\s%%'

Это также читает chksum1 только 2 раза, по сравнению с примером на основе цикла, который считывает его один раз на строку chksum.

Повторное использование входных файлов из одного из других ответов:

samveen@precise:~/so$ cat chksum
eed0fc0313f790cec0695914f1847bca  ./a.txt
9ee9e1fffbb3c16357bf80c6f7a27574  ./b.txt
a91a408e113adce865cba3c580add827  ./c.txt

samveen@precise:~/so$ cat chksum1
eed0fc0313f790cec0695914f1847bca  ./a.txt
8ee9e1fffbb3c16357bf80c6f7a27574  ./b.txt
a91a408e113adce865cba3c580add827  ./d.txt

samveen@precise:~/so$ sort chksum chksum1 chksum1 |uniq -c | egrep '^\s+2\s' |sed 's%\s\+2\s%%'
8ee9e1fffbb3c16357bf80c6f7a27574  ./b.txt
a91a408e113adce865cba3c580add827  ./d.txt

Другое возможное решение (как предлагается в комментариях к вопросу) использовать diff в сочетании с sort:

diff <(sort chksum) <(sort chksum1) |grep '^>'

Выход:

samveen@precise:~/so$ diff <(sort chksum) <(sort chksum1) |grep '^>'
> 8ee9e1fffbb3c16357bf80c6f7a27574  ./b.txt
> a91a408e113adce865cba3c580add827  ./d.txt

Ответ 5 · Nikolai Popov

Простое решение:

diff -q chksum1 chksum

Ответ 6 · Debugger

Как насчет использования команды grep. Каждая строка, которую вы читаете из chksum, будет служить шаблоном поиска в chksum1. Если grep находит совпадение, "$?", который содержит возвращаемое значение grep, будет равен 0, в противном случае он будет равен 1.

while read e; do
  grep $e checksum1
  if[ $? == "0" ];then
     echo $e "is the same"
  else
     echo $e "has been changed"
  fi
done < chksum

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы