Remova determinados campos de uma linha

2

Eu tenho as seguintes linhas em um arquivo:

Modified folders: html/project1/old/dev/vendor/symfony/yaml/Tests/bla.yml
Modified folders: html/port5/.DS_Store
Modified folders: html/trap/dev8/.DS_Store
Modified folders: html/bla3/test/appl/.DS_Store
Modified folders: html/bla4/pro1/app/bla/Api2.php
Modified folders: html/bla10/dev/appl/language/.DS_Store
Modified folders: html/bla11/dev/appl/language/abc.txt

Isso é basicamente saída de rsync. Gostaria de listar todas as linhas do arquivo em até 3 locais de diretório, como

Modified folders: html/project1/old
Modified folders: html/port5
Modified folders: html/trap/dev8
Modified folders: html/bla3/test
Modified folders: html/bla4/pro1
Modified folders: html/bla10/dev
Modified folders: html/bla11/dev

Alguém pode me fornecer algum comando ou script de shell para fazer o mesmo?

john deo
fonte
sua postagem foi destruída pela formatação de remarcação. Por favor edite seu post e colar a saída exatamente, em seguida, formatá-lo como código para que possamos ver os caracteres reais. Gostaria de editá-lo com prazer, mas, no momento, não sei dizer se seus arquivos realmente contêm >caracteres no início das linhas e quebras de linha duplas ou se é sua tentativa de formatar o conteúdo com precisão.
Zanna
@Zanna já assumiu o último e fixo a formatação como eu teria esperado que ...
Byte Comandante
john deo ... você reverteu a edição do Byte Commander, por isso ainda não sabemos o que está realmente no seu arquivo __
Zanna
@ Zanna Também editei a pergunta para adicionar blocos de código. Terceira vez com sorte?
WinEunuuchs2Unix 22/01

Respostas:

6

Talvez assim:

$ sed -r 's|/[^/]*$||' file | sed -r 's|([^/]*/?[^/]*/?[^/]*).*|\1|'
Modified folders: html/project1/old
Modified folders: html/port5
Modified folders: html/trap/dev8
Modified folders: html/bla3/test
Modified folders: html/bla4/pro1
Modified folders: html/bla10/dev
Modified folders: html/bla11/dev

Ou você pode fazer a segunda parte com cut:

sed -r 's|/[^/]*$||' file | cut -d '/' -f 1,2,3

Notas

  • -r use ERE
  • s|old|new|substitua oldpornew
  • [^/]* qualquer número de caracteres que não sejam /
  • $ fim da linha
  • /? zero ou um /
  • (pattern)salve patternpara fazer referência mais tarde com\1
  • .* qualquer número de caracteres
  • | tubo de revestimento (sem aspas) - passa a saída do comando do lado esquerdo para o comando do lado direito
  • cut -d '/'use /como delimitador
  • -f 1,2,3 imprima os três primeiros campos
Zanna
fonte
Não corresponde à segunda linha de exemplo, o OP também quer remover os nomes de arquivos ...
Byte Commander
Minha tentativa teria sido grep -Po '^.*?(/[^/]*){0,2}(?=/|$)', mas isso sofre o mesmo problema, e não consigo descobrir agora. Sinta-se livre para usá-lo, se isso lhe ajudar.
Byte Commander
@ByteCommander obrigado por isso. Consertei, mas não gosto de tubulação. Vou procurar uma maneira mais agradável
Zanna
@zanna, graças a uma tonelada. Obrigado por simplificar as coisas para mim .. eu realmente preciso aprender scripts de shell ... De qualquer forma, obrigado. Tenha um ótimo dia companheiro.
john deo
@PerlDuck meu conhecimento de Perl é de nível sub-iniciante. Por favor, poste uma resposta :)
Zanna
3

O script a seguir fará (quase) o que você pede.

#!/usr/bin/env perl

use strict;
use warnings;

while(<DATA>) {
    s!^(Modified\s+folders:\s+)((?:[^/]+/){1,3}).*?$!$1$2!;
    print;
}

__DATA__
Modified folders: html/project1/old/dev/vendor/symfony/yaml/Tests/bla.yml
Modified folders: html/port5/.DS_Store
Modified folders: html/trap/dev8/.DS_Store
Modified folders: html/bla3/test/appl/.DS_Store
Modified folders: html/bla4/pro1/app/bla/Api2.php
Modified folders: html/bla10/dev/appl/language/.DS_Store
Modified folders: html/bla11/dev/appl/language/abc.txt

Ele lê todas as linhas de entrada, seleciona alguns valores (meus meios de regex), substitui a linha pelos valores selecionados e, finalmente, imprime a linha agora modificada (para STDOUT).

Resultado

Modified folders: html/project1/old/
Modified folders: html/port5/
Modified folders: html/trap/dev8/
Modified folders: html/bla3/test/
Modified folders: html/bla4/pro1/
Modified folders: html/bla10/dev/
Modified folders: html/bla11/dev/

Se escrevermos o regex em uma única linha:

s!^(Modified\s+folders:\s+)((?:[^/]+/){1,3}).*?$!$1$2!;

então parece um pouco assustador, mas na verdade é bem simples. O operador básico é o operador de substituição s/// do Perl.

s/foo/bar/;

substituirá toda ocorrência de foocom bar. snos permite alterar o delimitador de /para algo diferente. Eu usei um !aqui, então também poderíamos escrever

s!foo!bar!;

O !que não quer dizer notque é apenas um caráter arbitrário aqui. sLfooLbarL;funcionaria também. Fazemos isso porque, se usarmos o padrão /, precisaremos escapar /dos parâmetros (que são conhecidos como sintaxe do palito). Considere que queremos substituir o caminho /old/pathpor /new/path. Agora compare:

s/\/old\/path/\/new\/path/; # escaping of / needed
s!/old/path!/new/path!;     # no escaping of / needed (but of ! if we had one in the text)

Também podemos aplicar o xmodificador ao arquivo s///. Permite um espaço em branco arbitrário (até novas linhas e comentários) no padrão (lado esquerdo) para melhorar a legibilidade. Agora o loop pode ser escrito como:

while(<DATA>) {
    s!^                         # match beginning of line
      (Modified\s+folders:\s+)  # the word "Modified", followed by 1 ore more 
                                # whitespace \s+,
                                # the literal "folders:", also followed by 1 or 
                                # more whitespace.
                                # We capture that match in $1 (that's why we have 
                                # parens around it).
      (                         # begin of 2nd capture group (in $2)
        (?:                     #   begin a group that is NOT captured (because of the "?:"
         [^/]+/                 #   one or more characters that are not a slash followed by a slash
        )                       #   end of group
        {1,3}                   #   this group should appear one to three times
      )                         # close capture group $2, i.e. remember the 1-3x slash thing
      .*?$                      # followed by arbitrary characters up to the end of line
     !$1$2!x;                   # Replace the line with the two found captures $1 and $2, i.e.
                                # with the text "Modified folders:" and the 1-3x slash thing.
    print;
}

O "script" completo também pode ser escrito como uma linha:

perl -pe 's!^(Modified\s+folders:\s+)((?:[^/]+/){1,3}).*?$!$1$2!x;' file

Atualizar

Acabei de perceber que a Modified folders:string também pode ser vista como um componente do caminho. Portanto, o padrão pode ser simplificado para

perl -pe 's!^((?:[^/]+/){1,3}).*?$!$1!;' file
PerlDuck
fonte
muito legal (+1)!
3
grep -oP '^.*?(/.*?){0,2}(?=/)'

uma breve explicação do dark regexp usado:

  • ^... eu começo da linha
  • .*?uma seq. de caracteres (mas apenas a quantidade necessária) para corresponder ao pré-caminho
  • /.*?){0,2} 0, 1 ou 2 diretórios
  • (?=/)expressão antecipada - seguida por uma /que não está incluída

fonte
Você poderia expandir sua resposta com detalhes sobre o que isso deve fazer?
DerHugo 22/01
Isso é muito mais interessante que a resposta Perl, porque também descarta o final /.
precisa saber é o seguinte