Сохраняйте изменения на месте с помощью NON GNU awk

9

Я столкнулся с вопросом (на самом SO), где OP должен выполнять редактирование и сохранять операции в самих Input_file (s).

Я знаю, что для одного Input_file мы могли бы сделать следующее:

awk '{print "test here..new line for saving.."}' Input_file > temp && mv temp Input_file

Теперь предположим, что нам нужно внести изменения в один и тот же формат файлов (предположим, здесь .txt).

Что я пробовал / думал для этой проблемы: его подход заключается в прохождении цикла for .txt файлов, и вызов singleawk- это болезненный и НЕ рекомендуемый процесс, поскольку он будет тратить ненужные циклы ЦП и для большего количества файлов будет больше медленный.

Итак, что можно сделать здесь, чтобы выполнить редактирование на месте для нескольких файлов с NON GNU, awkкоторый не поддерживает опцию на месте. Я также прошел через эту ветку Сохранение изменений на месте с помощью awk, но для NON GNU awk Vice нет ничего особенного и для изменения нескольких файлов на месте внутри awkсебя, так как не GNU awk не будет иметь inplaceопцию для этого.

ПРИМЕЧАНИЕ. Почему я добавляюbashтег, поскольку в своей части ответа я использовал команды bash, чтобы переименовать временные файлы в их настоящие имена Input_file, поэтому добавляю их.



РЕДАКТИРОВАТЬ: В соответствии с комментарием Эда сэра, добавив здесь пример примеров, хотя назначение кода этого потока может быть использовано и для общего редактирования на месте.

Пример входного файла (ов):

cat test1.txt
onetwo three
tets testtest

cat test2.txt
onetwo three
tets testtest

cat test3.txt
onetwo three
tets testtest

Образец ожидаемого выхода:

cat test1.txt
1
2

cat test2.txt
1
2

cat test3.txt
1
2
RavinderSingh13
источник
1
Интересная и актуальная проблема awk ++
anubhava
1
@ RavinderSingh13, если у вас есть целая куча файлов для применения, почему бы не использовать один вызов awk(возможно, в подоболочке) или {...}закрытую группу, а затем записать результаты в нужный выходной файл (либо для каждого входного файла, или объединенный файл для всех входных файлов). Затем вы просто перенаправляете выходные данные вложенной или заключенной в скобки группы в текущий файл, в который выполняется запись? Простое включение строки входных файлов после awkкоманды будет последовательно обрабатывать все файлы (или что-то подобное) ??
Дэвид С. Ранкин
@ DavidC.Rankin, спасибо, что ответили на этот вопрос. Да, я опубликовал подобные вещи, которые вы говорите, сэр, мой ответ также опубликован в этом вопросе, позвольте мне узнать ваши взгляды на тот же сэр, ура.
RavinderSingh13
1
После некоторого сна и размышлений об этом я вижу 2 варианта (1) с awk {..} file1 .. fileXзаписью измененного файла, как, например, temp01и в вашей следующей итерации при обработке следующего файла, используйте a, mv -f tmp01 input01чтобы перезаписать входной файл с измененными данными; или (2) просто напишите новый каталог ./tmp/tmp01 ... ./tmp/tmp0Xво время выполнения awkскрипта и выполните цикл с файлами в ./tmpкаталоге и, например, mv -f "$i" "input_${i##*[^0-9]}"(или любым другим расширением, которое вам нужно, чтобы заменить старые входные файлы.
Дэвид К. Ранкин
@ DavidC.Rankin, Спасибо, что сообщили о своих взглядах здесь, сэр, IMHO 1-й вариант может быть немного рискованным, так как мы делаем что-то без awkполного завершения кода, 2-й вариант почти такой же, как я использую в моем предложении, будет будьте благодарны, если вы можете сообщить свои мысли об этом решении, сэр.
RavinderSingh13

Ответы:

6

Поскольку главная цель этой темы - как сделать SAVE на месте в NON GNU, awkпоэтому я сначала публикую его шаблон, который поможет любому в любых требованиях, им нужно добавить / добавить BEGINи ENDраздел в своем коде, сохраняя свой основной БЛОК согласно их требование, и это должно сделать редактирование на месте тогда:

ПРИМЕЧАНИЕ. После этого все выходные данные будут записаны в файл output_file, поэтому, если вы хотите напечатать что-либо в стандартный вывод, добавьте толькоprint...оператор без> (out)последующего.

Общий шаблон:

awk -v out_file="out" '
FNR==1{
close(out)
out=out_file count++
rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
    .....your main block code.....
}
END{
 if(rename){
   system(rename)
 }
}
' *.txt


Конкретное предоставленное решение образца:

Я придумал следующий подход внутри awkсебя (для добавленных примеров мой подход состоит в том, чтобы решить эту проблему и сохранить результат в самом Input_file)

awk -v out_file="out" '
FNR==1{
  close(out)
  out=out_file count++
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
  print FNR > (out)
}
END{
  if(rename){
    system(rename)
  }
}
' *.txt

ПРИМЕЧАНИЕ: это всего лишь тест для сохранения отредактированных выходных данных в самих файлах Input_file, можно использовать его раздел BEGIN вместе с разделом END в их программе, основной раздел должен соответствовать требованию конкретного вопроса.

Справедливое предупреждение: также, так как этот подход создает новый временный выходной файл в пути, поэтому лучше убедиться, что у нас достаточно места в системах, хотя в конечном результате он будет сохранять только основные входные_файлы, но во время операций ему нужно пространство в каталоге system /



Ниже приведен тест для приведенного выше кода.

Выполнение программы на примере: Предположим, что следующие файлы.txtInput_file:

cat << EOF > test1.txt
onetwo three
tets testtest
EOF

cat << EOF > test2.txt
onetwo three
tets testtest
EOF

cat << EOF > test3.txt
onetwo three
tets testtest
EOF

Теперь, когда мы запускаем следующий код:

awk -v out_file="out" '
FNR==1{
  close(out)
  out=out_file count++
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"
}
{
  print "new_lines_here...." > (out)
}
END{
  if(rename){
    system("ls -lhtr;" rename)
  }
}
' *.txt

ПРИМЕЧАНИЕ. Я специально разместилls -lhtrвsystemразделе, чтобы увидеть, какие выходные файлы он создает (временная основа), потому что позже он переименует их в их фактическое имя.

-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test2.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test1.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test3.txt
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out2
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out1
-rw-r--r-- 1 runner runner  38 Dec  9 05:33 out0

Когда мы выполняем сценарий ls -lhtrпосле awkзапуска, мы можем видеть только .txtфайлы там.

-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test2.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test1.txt
-rw-r--r-- 1 runner runner  27 Dec  9 05:33 test3.txt


Пояснение: Добавление подробного объяснения вышеупомянутой команды здесь:

awk -v out_file="out" '                                    ##Starting awk program from here, creating a variable named out_file whose value SHOULD BE a name of files which are NOT present in our current directory. Basically by this name temporary files will be created which will be later renamed to actual files.
FNR==1{                                                    ##Checking condition if this is very first line of current Input_file then do following.
  close(out)                                               ##Using close function of awk here, because we are putting output to temp files and then renaming them so making sure that we shouldn't get too many files opened error by CLOSING it.
  out=out_file count++                                     ##Creating out variable here, whose value is value of variable out_file(defined in awk -v section) then variable count whose value will be keep increment with 1 whenever cursor comes here.
  rename=(rename?rename ORS:"") "mv \047" out "\047 \047" FILENAME "\047"     ##Creating a variable named rename, whose work is to execute commands(rename ones) once we are done with processing all the Input_file(s), this will be executed in END section.
}                                                          ##Closing BLOCK for FNR==1  condition here.
{                                                          ##Starting main BLOCK from here.
  print "new_lines_here...." > (out)                       ##Doing printing in this example to out file.
}                                                          ##Closing main BLOCK here.
END{                                                       ##Starting END block for this specific program here.
  if(rename){                                              ##Checking condition if rename variable is NOT NULL then do following.
    system(rename)                                         ##Using system command and placing renme variable inside which will actually execute mv commands to rename files from out01 etc to Input_file etc.
  }
}                                                          ##Closing END block of this program here.
' *.txt                                                    ##Mentioning Input_file(s) with their extensions here.
RavinderSingh13
источник
1
Интересный факт: если вы удалите входной файл в FNR==1блоке, вы все равно можете сохранить изменения на месте. Как awk 'FNR==1{system("rm " FILENAME)} {print "new lines" > FILENAME}' files.... Это вообще ненадежно (полная потеря данных, скорее всего, произойдет), но, тем не менее, в основном работает нормально: D
oguz ismail
1
Очень хорошо объясненный
обходной путь
3

Я бы, наверное, пошел с чем-то вроде этого, если бы я попытался сделать это:

$ cat ../tst.awk
FNR==1 { saveChanges() }
{ print FNR > new }
END { saveChanges() }

function saveChanges(   bak, result, mkBackup, overwriteOrig, rmBackup) {
    if ( new != "" ) {
        bak = old ".bak"
        mkBackup = "cp \047" old "\047 \047" bak "\047; echo \"$?\""
        if ( (mkBackup | getline result) > 0 ) {
            if (result == 0) {
                overwriteOrig = "mv \047" new "\047 \047" old "\047; echo \"$?\""
                if ( (overwriteOrig | getline result) > 0 ) {
                    if (result == 0) {
                        rmBackup = "rm -f \047" bak "\047"
                        system(rmBackup)
                    }
                }
            }
        }
        close(rmBackup)
        close(overwriteOrig)
        close(mkBackup)
    }
    old = FILENAME
    new = FILENAME ".new"
}

$ awk -f ../tst.awk test1.txt test2.txt test3.txt

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

Обратите внимание, что если у вас есть исходные файлы с именем whatever.bakили whatever.newв вашем каталоге, вы бы перезаписали их временными файлами, поэтому вам также нужно добавить тест для этого. Вызов для mktempполучения имен временных файлов будет более надежным.

FAR более полезной вещью, которая будет иметься в этой ситуации, будет инструмент, который выполняет любую другую команду и выполняет часть редактирования «на месте», поскольку она может использоваться для обеспечения редактирования «на месте» для POSIX sed, awk, grep, tr, чего угодно и не требует, чтобы вы меняли синтаксис вашего скрипта на print > outи т. д. каждый раз, когда вы хотите напечатать значение. Простой, хрупкий пример:

$ cat inedit
#!/bin/env bash

for (( pos=$#; pos>1; pos-- )); do
    if [[ -f "${!pos}" ]]; then
        filesStartPos="$pos"
    else
        break
    fi
done

files=()
cmd=()
for (( pos=1; pos<=$#; pos++)); do
    arg="${!pos}"
    if (( pos < filesStartPos )); then
        cmd+=( "$arg" )
    else
        files+=( "$arg" )
    fi
done

tmp=$(mktemp)
trap 'rm -f "$tmp"; exit' 0

for file in "${files[@]}"; do
    "${cmd[@]}" "$file" > "$tmp" && mv -- "$tmp" "$file"
done

который вы бы использовали следующим образом:

$ awk '{print FNR}' test1.txt test2.txt test3.txt
1
2
1
2
1
2

$ ./inedit awk '{print FNR}' test1.txt test2.txt test3.txt

$ tail test1.txt test2.txt test3.txt
==> test1.txt <==
1
2

==> test2.txt <==
1
2

==> test3.txt <==
1
2

Одна очевидная проблема с этим ineditсценарием - это сложность идентификации файлов ввода-вывода отдельно от команды, когда у вас есть несколько входных файлов. В приведенном выше сценарии предполагается, что все входные файлы отображаются в виде списка в конце команды, и команда запускается для них по одному, но, конечно, это означает, что вы не можете использовать его для сценариев, которым требуется 2 или более файлов в время, например:

awk 'NR==FNR{a[$1];next} $1 in a' file1 file2

или сценарии, которые устанавливают переменные между файлами в списке аргументов, например:

awk '{print $7}' FS=',' file1 FS=':' file2

Делая его более надежным, оставьте его в качестве упражнения для читателя, но посмотрите на xargsсинопсис как на отправную точку того, как ineditдолжен работать робаст :-).

Эд Мортон
источник
0

Решение оболочки простое и, вероятно, достаточно быстрое:

for f in *.txt
do  awk '...' $f > $f.tmp
    mv $f.tmp $f
done

Ищите другое решение только в том случае, если вы убедительно продемонстрировали, что это слишком медленно. Помните: преждевременная оптимизация - корень всего зла.

user448810
источник
Спасибо за ваш ответ, но, как уже упоминалось в моем вопросе, мы знаем об этом ответе, но это действительно излишнее выполнение этой задачи, поэтому я упомянул, если бы мы могли попробовать что-то в самом awk. Спасибо за потраченное время и отвечу здесь ура.
RavinderSingh13