Збережіть зміни на місці за допомогою 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 тут).

Що я спробував / подумав у цій проблемі: її підхід проходить через цикл файлів .txt та виклик синглуawk- це болісний і НЕ рекомендований процес, оскільки це витратить зайві цикли процесора і для більшої кількості файлів було б більше повільний.

Отже, що, можливо, можна зробити тут для редагування на місці декількох файлів з NON GNU, awkякий не підтримує опцію inplace. Я також пройшов цей потік Збережіть модифікації на місці з awk, але для NON GNU awk vice і зміни декількох файлів на місці всередині нічого не існує нічого awk, оскільки не в GNU awk не буде inplaceможливості для нього.

ПРИМІТКА: Чому я додаюbashтег, оскільки у своїй частині відповідей я використовував команди bash для перейменування тимчасових файлів у їхні фактичні імена Input_file і додаючи його.



EDIT: Відповідно до коментаря Ед-сер, додаючи сюди приклад зразків, хоча мета коду цього потоку може бути використана і для загальної мети.

Зразок вхідних_файлів:

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

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]}"(або будь-яким розширенням, необхідним для заміни старих вхідних файлів.
David C. Rankin

@ DavidC.Rankin, Дякую, що ви повідомили ваші погляди тут, сер, ІМХО 1-й варіант може бути трохи ризиком, оскільки ми робимо щось без awkповного заповнення коду, другий варіант майже такий же, як я використовую в своїх пропозиціях, буде будьте вдячні, якщо ви можете повідомити свої думки щодо цього рішення, сер.
RavinderSingh13

Відповіді:


6

Оскільки головна мета цього потоку - як замінити ЗБЕРЕГТИ в 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 (s), можна використовувати його BEGIN розділ разом із його розділом END у своїй програмі, головний розділ повинен відповідати вимогам самого конкретного питання.

Справедливе попередження: Крім того, оскільки цей підхід робить новий тимчасовий файл у шляху, тому краще переконайтесь, що у нас є достатньо місця в системах, хоча при кінцевому результаті це збереже лише основні вхідні_файли (файли), але під час операцій йому потрібен простір у системі / каталозі



Далі йде тест на наведений вище код.

Виконання програми на прикладі: Припустимо, наступними є.txtвхідні_файли:

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.

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 - це інструмент, який виконує будь-яку іншу команду та робить частину редагування "inplace", оскільки це може бути використано для редагування "inplace" для 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

Шукайте інше рішення лише тоді, коли ви остаточно продемонстрували, що це занадто повільно. Пам'ятайте: передчасна оптимізація - корінь усього зла.


Дякую за вашу відповідь, але, як було сказано в самому моєму запитанні, ми знаємо про цю відповідь, але це справді надмірне виконання цього завдання, тому я зазначив, чи зможемо ми спробувати щось у самому пробудженні. Дякую за ваш час і відповідайте тут привіт.
RavinderSingh13
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.