Salve as modificações no local com o NON GNU awk

9

Eu me deparei com uma pergunta (no próprio SO) em que o OP precisa editar e salvar a operação no próprio Input_file (s).

Sei por um único arquivo Input_file que poderíamos fazer o seguinte:

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

Agora, digamos que precisamos fazer alterações no mesmo tipo de formato de arquivo (assuma .txt aqui).

O que tentei / pensei para este problema: Sua abordagem é passar por um loop for de arquivos .txt e chamar singleawké um processo doloroso e NÃO recomendado, pois desperdiçará ciclos desnecessários da CPU e, para um número maior de arquivos, seria mais lento.

Então, o que possivelmente poderia ser feito aqui para executar edição no local de vários arquivos com um NON GNU awkque não suporta a opção inplace. Também passei por esse segmento. Salvar modificações no local com o awk, mas não há muito para o vice NÃO do GNU awk e alterar vários arquivos no local awk, pois um awk não GNU não terá inplaceopção.

OBSERVAÇÃO: Por que estou adicionandobashtags, desde que, na parte de resposta, usei os comandos bash para renomear arquivos temporários para seus nomes reais de Input_file, adicionando-os.



EDIT: De acordo com o comentário de Ed sir, adicionando um exemplo de amostras aqui, embora a finalidade do código deste segmento também possa ser usada por edição genérica no local.

Arquivos de entrada de amostra:

cat test1.txt
onetwo three
tets testtest

cat test2.txt
onetwo three
tets testtest

cat test3.txt
onetwo three
tets testtest

Amostra da saída esperada:

cat test1.txt
1
2

cat test2.txt
1
2

cat test3.txt
1
2
RavinderSingh13
fonte
11
Problema awk interessante e pertinente ++
anubhava
11
@ RavinderSingh13 se você tem um monte de arquivos para aplicar isso, por que não usar uma única chamada para awk(talvez em um subshell) ou um {...}grupo fechado e depois gravar os resultados no arquivo de saída desejado (para cada arquivo de entrada, ou um arquivo combinado para todos os arquivos de entrada). Então você simplesmente redireciona a saída do subshell ou grupo de chaves para o arquivo atual que está sendo gravado? Simplesmente incluir uma sequência de arquivos de entrada após o awkcomando processaria sequencialmente todos os arquivos (ou algo semelhante)?
David C. Rankin
@ DavidC.Rankin, obrigado por responder a este. Sim, eu publiquei coisas semelhantes que você está dizendo senhor, minha resposta também está publicada nesta pergunta, deixe-me saber sua opinião sobre o mesmo senhor, aplausos.
usar o seguinte
11
Depois de dormir um pouco e pensar nisso, vejo duas opções (1) com awk {..} file1 .. fileXescrever o arquivo modificado, como, por exemplo, temp01e em sua próxima iteração ao processar o próximo arquivo, use a mv -f tmp01 input01para sobrescrever o arquivo de entrada com os dados modificados; ou (2) basta escrever um novo diretório ./tmp/tmp01 ... ./tmp/tmp0Xdurante a execução do awkscript e acompanhar com um loop sobre os arquivos no ./tmpdiretório e, por exemplo mv -f "$i" "input_${i##*[^0-9]}"(ou qualquer expansão necessária para substituir os arquivos de entrada antigos)
David C. Rankin
@ DavidC.Rankin, Obrigado por informar seus pontos de vista aqui, senhor, a primeira opção do IMHO pode ser um pouco arriscada, pois estamos fazendo algo sem awka conclusão completa do código, a segunda opção é quase a mesma que eu estou usando na minha sugestão. fique agradecido se você puder informar seus pensamentos sobre essa solução, senhor.
precisa

Respostas:

6

Como o objetivo principal deste segmento é como fazer o SAVE local no GNU, awkpor isso estou publicando primeiro seu modelo que ajudará qualquer pessoa em qualquer tipo de requisito, eles precisam adicionar / acrescentar BEGINe ENDseção em seu código, mantendo seu BLOCO principal conforme seus requisito e deve fazer a edição local:

OBSERVAÇÃO: O seguinte gravará toda a saída em output_file; caso deseje imprimir alguma coisa na saída padrão, adicione apenas aprint...declaração sem as> (out)seguintes.

Modelo genérico:

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


Solução específica da amostra fornecida:

Eu vim com a seguinte abordagem em awksi (para exemplos adicionados a seguir é minha abordagem para resolver isso e salvar a saída no arquivo 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

NOTA: este é apenas um teste para salvar a saída editada nos arquivos de entrada, pode-se usar a seção BEGIN, juntamente com a seção END do programa, a seção principal deve ser conforme o requisito da pergunta específica.

Aviso justo: também como essa abordagem cria um novo arquivo temporário de saída no caminho, é melhor garantir que tenhamos espaço suficiente nos sistemas, embora no resultado final isso mantenha apenas os principais arquivos_de_ entrada, mas durante as operações ele precisará de espaço no sistema / diretório



A seguir, é apresentado um teste para o código acima.

Execução do programa com um exemplo: Vamos supor que sejam os.txtarquivos_de_ entrada:

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

Agora, quando executamos o seguinte código:

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

NOTA: Eu coloqueils -lhtrnasystemseção intencionalmente para ver quais arquivos de saída ele está criando (base temporária), porque mais tarde os renomeará para o nome real.

-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

Quando fazemos um script ls -lhtrapós awka execução, poderíamos ver apenas .txtarquivos lá.

-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


Explicação: Incluindo uma explicação detalhada do comando acima aqui:

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
fonte
11
Curiosidade: se você excluir o arquivo de entrada no FNR==1bloco, ainda poderá salvar as alterações no local. Like awk 'FNR==1{system("rm " FILENAME)} {print "new lines" > FILENAME}' files.... Isso não é confiável (é provável que a perda completa de dados ocorra), mas ainda assim funciona principalmente: D
oguz ismail
11
Muito bem explicado trabalho-around
anubhava
3

Eu provavelmente usaria algo assim se tentasse fazer isso:

$ 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

Eu preferiria copiar o arquivo original para o backup primeiro e depois operar para salvar as alterações no original, mas isso mudaria o valor da variável FILENAME para cada arquivo de entrada indesejável.

Observe que, se você tiver um arquivo original com o nome whatever.bakou whatever.newem seu diretório, substitua-o por um arquivo temporário, para que você também precise adicionar um teste. Uma chamada para mktempobter os nomes dos arquivos temporários seria mais robusta.

A coisa MUITO mais útil a ter nessa situação seria uma ferramenta que executa qualquer outro comando e faz a parte de edição "local", pois isso poderia ser usado para fornecer edição "local" para POSIX sed, awk, grep, tr, o que for e não exigiria que você altere a sintaxe do seu script para print > outetc. sempre que desejar imprimir um valor. Um exemplo simples e frágil:

$ 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

que você usaria da seguinte maneira:

$ 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

Um problema óbvio com esse ineditscript é a dificuldade de identificar os arquivos de entrada / saída separadamente do comando quando você possui vários arquivos de entrada. O script acima supõe que todos os arquivos de entrada apareçam como uma lista no final do comando e o comando é executado contra eles um de cada vez, mas é claro que isso significa que você não pode usá-lo para scripts que requerem 2 ou mais arquivos em um tempo, por exemplo:

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

ou scripts que definem variáveis ​​entre arquivos na lista arg, por exemplo:

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

Tornando-o mais robusto deixado como um exercício para o leitor, mas olhe para a xargssinopse como um ponto de partida para como um robusto ineditprecisaria funcionar :-).

Ed Morton
fonte
0

A solução shell é simples e provavelmente rápida o suficiente:

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

Procure uma solução diferente apenas se tiver demonstrado conclusivamente que isso é muito lento. Lembre-se: a otimização prematura é a raiz de todo mal.

user448810
fonte
Obrigado pela sua resposta, mas, como mencionado na minha pergunta, estamos cientes dessa resposta, mas isso é realmente um exagero ao executar esta tarefa; foi por isso que mencionei se poderíamos tentar algo dentro do próprio awk. Obrigado pelo seu tempo e responda aqui felicidades.
precisa