使用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文件的for循环,并且调用singleawk是一个痛苦且不建议的过程,因为这将浪费不必要的cpu​​周期,并且对于更多数量的文件,它将更多慢。

因此,可以使用awk不支持inplace选项的NON GNU对多个文件执行就地编辑。我也经历了这个线程,用awk将修改保存到位,但对于NON GNU awk副工具和更改其内部的多个文件并没有多大作用awk,因为非GNU awk将没有inplace选择权。

注意:为什么要添加bash标签,因为在答案部分中,我已经使用bash命令将临时文件重命名为其实际的Input_file名称,因此添加了它。



编辑:根据Ed先生的评论,在此处添加了示例示例,尽管该线程代码的目的也可以由通用目的就地编辑使用。

样本输入文件:

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(可能在子外壳中)或{...}封闭的组,然后将结果写入所需的输出文件(对于每个输入文件,或所有输入文件的组合文件)。然后,您只需将subshel​​l或括号括起来的组的输出重定向到正在写入的当前文件?只需在awk命令后包含一串输入文件,即可依次处理所有文件(或类似文件)?
戴维·C·兰金

@ DavidC.Rankin,感谢您对这一答复。是的,我已经张贴了类似的话,您说的先生,我的答案也张贴在这个问题中,我知道您对同一先生的看法,干杯。
RavinderSingh19年

1
经过一番思考之后,我看到2个选项(1),awk {..} file1 .. fileX将修改后的文件写为:例如temp01,在下一个迭代中,当处理下一个文件时,使用a mv -f tmp01 input01将修改后的数据覆盖输入文件;或(2)只需./tmp/tmp01 ... ./tmp/tmp0Xawk脚本执行期间编写一个新目录,然后循环遍历该目录中的./tmp文件即可,例如mv -f "$i" "input_${i##*[^0-9]}"(或需要替换旧输入文件的任何扩展名。)
David C. Rankin

@ DavidC.Rankin,谢谢您在这里告诉您的先生,恕我直言,第一个选项可能会有点风险,因为我们正在做的事情没有awk完整的代码完成,所以第二个选项与我在建议中使用的几乎相同先生,请告诉我们您的想法,不胜感激。
RavinderSingh19年

Answers:


6

由于此线程的主要目标是如何在NON GNU中进行SAVE的存储,awk因此我首先发布其模板,该模板将对任何要求的人有所帮助,因此他们需要在代码中添加/附加BEGINEND分段,以保持其主BLOCK不变。要求,然后应进行就地编辑:

注意:以下命令会将其所有输出写入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部分,主要部分应根据特定问题本身的要求。

合理的警告:此外,由于此方法会在路径中创建一个新的临时out文件,因此最好确保我们在系统上有足够的空间,尽管最终结果将仅保留主要的Input_file,但在操作过程中它需要系统/目录上的空间



以下是上面代码的测试。

举例说明程序的执行:假设以下是.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 -lhtrawk脚本后,我们只能.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
很好地解释了变通方法
anubhava

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每次要打印值时都将脚本的语法更改为etc。一个简单,易碎的示例:

$ 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脚本的一个明显问题是,当您有多个输入文件时,很难从命令中分别识别输入/输出文件。上面的脚本假定所有输入文件都在命令末尾显示为列表,并且命令一次对它们运行,但这当然意味着您不能将其用于需要两个或更多文件的脚本时间,例如:

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

或在arg列表中的文件之间设置变量的脚本,例如:

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

使其更健壮,作为练习供读者阅读,但请以xargs提要为起点来了解健壮inedit将如何工作:-)。


0

Shell解决方案很简单,而且可能足够快:

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

仅在确定性地证明这太慢时,才搜索其他解决方案。请记住:过早的优化是万恶之源。


谢谢您的答复,但是正如我在问题本身中提到的那样,我们知道这个答案,但这确实是执行此任务的过大杀伤力,这就是为什么我提到如果我们可以在awk本身中尝试某些事情的原因。谢谢您的时间,在这里为您加油。
RavinderSingh19年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.