Lưu các sửa đổi tại chỗ với NON GNU awk


9

Tôi đã bắt gặp một câu hỏi (trên chính SO) trong đó OP phải thực hiện chỉnh sửa và lưu hoạt động vào chính Input_file (s).

Tôi biết với một Input_file duy nhất chúng ta có thể làm như sau:

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

Bây giờ hãy nói rằng chúng ta cần thực hiện các thay đổi trong cùng loại định dạng của tệp (giả sử .txt ở đây).

Những gì tôi đã cố gắng / nghĩ cho vấn đề này: Cách tiếp cận của nó là trải qua một vòng lặp các tệp .txt và gọi đơnawklà một quá trình đau đớn và KHÔNG được đề xuất, vì nó sẽ lãng phí các chu kỳ cpu không cần thiết và với số lượng tệp nhiều hơn sẽ nhiều hơn chậm

Vì vậy, những gì có thể được thực hiện ở đây để thực hiện chỉnh sửa tại chỗ cho nhiều tệp với NON GNU awkkhông hỗ trợ tùy chọn tại chỗ. Tôi cũng đã trải qua chủ đề này Lưu các sửa đổi tại chỗ với awk nhưng không có gì nhiều cho NON GNU awk vice và thay đổi nhiều tệp trong awkđó, vì một awk GNU không có inplacetùy chọn này.

LƯU Ý: Tại sao tôi thêmbashthẻ vì trong phần trả lời của tôi, tôi đã sử dụng các lệnh bash để đổi tên các tệp tạm thời thành tên Input_file thực tế của chúng để thêm nó.



EDIT: Theo nhận xét của Ed, thêm một ví dụ về các mẫu ở đây, mặc dù mục đích của mã của chủ đề này cũng có thể được sử dụng cho mục đích chung là chỉnh sửa tại chỗ.

Mẫu Input_file (s):

cat test1.txt
onetwo three
tets testtest

cat test2.txt
onetwo three
tets testtest

cat test3.txt
onetwo three
tets testtest

Mẫu sản lượng dự kiến:

cat test1.txt
1
2

cat test2.txt
1
2

cat test3.txt
1
2

1
Vấn đề awk thú vị và thích hợp ++
anubhava

1
@ RavinderSingh13 nếu bạn có cả đống tệp để áp dụng điều này, tại sao không sử dụng một cuộc gọi đến awk, (có thể trong một mạng con) hoặc một {...}nhóm kèm theo và sau đó ghi kết quả vào tệp đầu ra mong muốn (cho mỗi tệp đầu vào, hoặc một tệp kết hợp cho tất cả các tệp đầu vào). Sau đó, bạn chỉ cần chuyển hướng đầu ra của nhóm con hoặc nhóm được đóng dấu vào tệp hiện tại đang được ghi vào? Đơn giản chỉ cần bao gồm một chuỗi các tệp đầu vào theo awklệnh sẽ xử lý tuần tự tất cả các tệp (hoặc một cái gì đó tương tự) ??
David C. Rankin

@ DavidC.Rankin, cảm ơn bạn đã trả lời về điều này. Vâng tôi đã đăng loại tương tự như bạn nói thưa ông, câu trả lời của tôi cũng được đăng trong câu hỏi này tôi biết quan điểm của bạn về cùng thưa ông, chúc mừng.
RavinderSingh13

1
Sau khi ngủ và suy nghĩ về nó, tôi thấy 2 tùy chọn (1) với việc awk {..} file1 .. fileXghi tệp đã sửa đổi là, ví dụ temp01và trong lần lặp tiếp theo của bạn trong khi xử lý tệp tiếp theo, hãy sử dụng mv -f tmp01 input01ghi đè lên tệp đầu vào với dữ liệu đã sửa đổi; hoặc (2) chỉ cần viết một thư mục mới ./tmp/tmp01 ... ./tmp/tmp0Xtrong quá trình thực thi awktập lệnh và theo dõi với một vòng lặp trên các tệp trong ./tmpthư mục và, ví dụ mv -f "$i" "input_${i##*[^0-9]}"(hoặc bất kỳ bản mở rộng nào bạn cần để thay thế các tệp đầu vào cũ.
David C. Rankin

@ DavidC.Rankin, Cảm ơn bạn đã cho biết quan điểm của mình ở đây thưa ông, tùy chọn IMHO 1st có thể có một chút rủi ro, vì chúng tôi đang làm gì đó mà không awkhoàn thành mã hoàn chỉnh, tùy chọn thứ 2 gần giống với những gì tôi đang sử dụng trong đề xuất của mình, sẽ hãy biết ơn nếu bạn có thể cho những suy nghĩ của bạn biết về giải pháp đó thưa ông.
RavinderSingh13

Câu trả lời:


6

Vì mục đích chính của chủ đề này là làm thế nào để thực hiện TIẾT KIỆM trong NON GNU, awkvì vậy tôi sẽ đăng mẫu đầu tiên của nó để giúp bất kỳ ai trong bất kỳ yêu cầu nào, họ cần thêm / nối BEGINENDphần trong mã của họ giữ BLOCK chính của họ theo yêu cầu và nó nên thực hiện chỉnh sửa tại chỗ sau đó:

LƯU Ý: Sau đây sẽ ghi tất cả đầu ra của nó vào output_file, vì vậy trong trường hợp bạn muốn in bất cứ điều gì sang đầu ra tiêu chuẩn, vui lòng chỉ thêmprint...câu lệnh mà không cần> (out)theo dõi.

Mẫu chung:

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


Giải pháp cụ thể của mẫu được cung cấp:

Tôi đã đưa ra cách tiếp cận sau trong awkchính nó (đối với các mẫu được thêm vào sau đây là cách tiếp cận của tôi để giải quyết vấn đề này và lưu đầu ra vào chính 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

LƯU Ý: đây chỉ là một thử nghiệm để lưu đầu ra được chỉnh sửa vào chính Input_file, người ta có thể sử dụng phần BEGIN của nó, cùng với phần END của nó trong chương trình của họ, phần chính phải theo yêu cầu của chính câu hỏi cụ thể.

Cảnh báo công bằng: Ngoài ra, vì cách tiếp cận này tạo ra một tệp tạm thời mới trong đường dẫn để đảm bảo rằng chúng tôi có đủ dung lượng trên các hệ thống, mặc dù ở kết quả cuối cùng, nó sẽ chỉ giữ lại Input_file (s) nhưng trong quá trình hoạt động, nó cần không gian trên hệ thống / thư mục



Sau đây là một bài kiểm tra cho mã trên.

Thực thi chương trình với một ví dụ: Hãy giả sử sau đây là.txtInput_file (s):

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

Bây giờ khi chúng tôi chạy mã sau đây:

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

LƯU Ý: Tôi có chỗ đứngls -lhtrtrongsystemphần cố ý để xem tập tin đầu ra nó đang tạo (cơ sở tạm thời) vì sau đó nó sẽ đổi tên chúng thành tên thực tế của họ.

-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

Khi chúng tôi thực hiện một đoạn script ls -lhtrsau khi awkchạy xong, chúng tôi chỉ có thể thấy .txtcác tệp trong đó.

-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


Giải thích: Thêm một lời giải thích chi tiết về lệnh trên đây:

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
Thực tế thú vị: nếu bạn xóa tệp đầu vào trong FNR==1khối, bạn vẫn có thể lưu các thay đổi tại chỗ. Thích awk 'FNR==1{system("rm " FILENAME)} {print "new lines" > FILENAME}' files.... Điều này hoàn toàn không đáng tin cậy (mất dữ liệu hoàn toàn có khả năng xảy ra), tuy nhiên, nó vẫn hoạt động tốt: D
oguz ismail

1
Giải thích rất tốt về công việc
anubhava

3

Có lẽ tôi sẽ đi với một cái gì đó như thế này nếu tôi cố gắng làm điều này:

$ 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

Trước tiên, tôi muốn sao chép tệp gốc vào bản sao lưu và sau đó thao tác lưu các thay đổi đó vào bản gốc nhưng làm như vậy sẽ thay đổi giá trị của biến FILENAME cho mọi tệp đầu vào không mong muốn.

Lưu ý rằng nếu bạn có một tệp gốc có tên whatever.bakhoặc whatever.newtrong thư mục của mình thì bạn sẽ ghi đè lên chúng bằng các tệp tạm thời, do đó bạn cũng cần thêm một bài kiểm tra cho điều đó. Một cuộc gọi để mktempcó được tên tệp tạm thời sẽ mạnh mẽ hơn.

Điều FAR hữu ích hơn trong tình huống này sẽ là một công cụ thực thi bất kỳ lệnh nào khác và thực hiện phần chỉnh sửa "tại chỗ" vì nó có thể được sử dụng để cung cấp chỉnh sửa "tại chỗ" cho POSIX sed, awk, grep, tr, bất cứ điều gì và sẽ không yêu cầu bạn thay đổi cú pháp của tập lệnh thành print > outvv mỗi khi bạn muốn in một giá trị. Một ví dụ đơn giản, dễ vỡ:

$ 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

mà bạn sử dụng như sau:

$ 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

Một vấn đề rõ ràng với inedittập lệnh đó là khó khăn trong việc xác định các tệp đầu vào / đầu ra tách biệt với lệnh khi bạn có nhiều tệp đầu vào. Tập lệnh ở trên giả định tất cả các tệp đầu vào xuất hiện dưới dạng một danh sách ở cuối lệnh và lệnh được chạy với chúng cùng một lúc nhưng tất nhiên điều đó có nghĩa là bạn không thể sử dụng nó cho các tập lệnh yêu cầu 2 hoặc nhiều tệp tại một thời gian, ví dụ:

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

hoặc các tập lệnh đặt biến giữa các tệp trong danh sách arg, ví dụ:

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

Làm cho nó mạnh mẽ hơn để lại như một bài tập cho người đọc nhưng hãy xem xargstóm tắt như một điểm khởi đầu cho việc một người mạnh mẽ ineditsẽ cần phải làm việc như thế nào :-).


0

Giải pháp vỏ đơn giản và có khả năng đủ nhanh:

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

Chỉ tìm kiếm một giải pháp khác nếu bạn đã kết luận chứng minh rằng điều này quá chậm. Hãy nhớ rằng: tối ưu hóa sớm là gốc rễ của mọi tội lỗi.


Cảm ơn bạn đã trả lời nhưng như đã đề cập trong chính câu hỏi của tôi, chúng tôi biết câu trả lời này nhưng đây thực sự là quá mức khi thực hiện nhiệm vụ này, đó là lý do tại sao tôi đã đề cập nếu chúng ta có thể thử điều gì đó trong chính awk. Cảm ơn bạn đã dành thời gian và trả lời ở đây chúc mừng.
RavinderSingh13
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.