bash: maneira mais curta de obter a n-ésima coluna de saída


165

Digamos que, durante seu dia de trabalho, você encontre repetidamente a seguinte forma de saída em colunas de algum comando no bash (no meu caso, executando svn stno meu diretório de trabalho do Rails):

?       changes.patch
M       app/models/superman.rb
A       app/models/superwoman.rb

para trabalhar com a saída do seu comando - nesse caso, os nomes dos arquivos - é necessário algum tipo de análise para que a segunda coluna possa ser usada como entrada para o próximo comando.

O que eu tenho feito é usar awkpara chegar à segunda coluna, por exemplo, quando eu quero remover todos os arquivos (não que isso seja um típico caso de usuário :), eu faria:

svn st | awk '{print $2}' | xargs rm

Como eu digito muito isso, uma pergunta natural é: existe uma maneira mais curta (portanto mais fria) de fazer isso no bash?

NOTA: O que estou perguntando é essencialmente uma pergunta de comando do shell, mesmo que meu exemplo concreto esteja no meu fluxo de trabalho svn. Se você achar que o fluxo de trabalho é tolo e sugerir uma abordagem alternativa, provavelmente não votarei contra você, mas outros podem, já que a questão aqui é realmente como obter o comando da n-ésima coluna no bash, da maneira mais curta possível . Obrigado :)


1
Quando você usa um comando com frequência, é melhor criar um script e colocá-lo no seu caminho. Você pode simplesmente criar uma função no seu bashrc, se preferir. Não vejo o ponto de reduzir a expressão de seleção de coluna.
— Lynch

1
Você está certo, e eu posso fazer isso. O 'ponto' é a busca de novas maneiras de fazer coisas em bash, para fins de aprendizagem, mas principalmente para se divertir :)
— sv1

1
Além disso, você não possui o .bashrc ao fazer a transferência para algum lugar, por isso é útil conhecer o caminho sem ele.
— Kos

Respostas:


125

Você pode usar cutpara acessar o segundo campo:

cut -f2

Edit: Desculpe, não percebi que o SVN não usa guias em sua saída, então isso é um pouco inútil. Você pode personalizar cuta saída, mas é um pouco frágil - algo como cut -c 10- funcionaria, mas o valor exato dependerá da sua configuração.

Outra opção é algo como: sed 's/.\s\+//'


1
Tente usar cut -f2com a svn stsaída. Você verá que não funciona.
— Lynch

Presumi que o real svn stusaria tabulações em sua saída. Após alguns testes, parece que esse não é o caso. Droga.
— Pors

21
Passar um delimitador apropriado para -dpermitir que isso funcione.
— Yogh

6
Para expandir o que @Yogh disse, para espaços como o delimitador, seria semelhante cut -d" " -f2.
— adamyonk

Sem awk no stdout, use xargs: svn st | xargs | cut -d "" -f2
— vr286 30/01

106

Para realizar o mesmo que:

svn st | awk '{print $2}' | xargs rm

usando apenas o bash, você pode usar:

svn st | while read a b; do rm "$b"; done

Concedido, não é mais curto, mas é um pouco mais eficiente e lida com o espaço em branco nos seus nomes de arquivos corretamente.


O que é ae bcomo obtê-los?
— Timo

1
@Timo arepresenta a primeira coluna e bas colunas restantes. Se você deseja imprimir a segunda coluna, use read a b c;e use em echovez de rm. Eu usei isso para obter vários processos de IDs que seguiam um padrão grep, para que eu pudesse interromper todos eles.
— precisa

36

Eu me encontrei na mesma situação e acabei adicionando esses aliases ao meu .profilearquivo:

alias c1="awk '{print \$1}'"
alias c2="awk '{print \$2}'"
alias c3="awk '{print \$3}'"
alias c4="awk '{print \$4}'"
alias c5="awk '{print \$5}'"
alias c6="awk '{print \$6}'"
alias c7="awk '{print \$7}'"
alias c8="awk '{print \$8}'"
alias c9="awk '{print \$9}'"

O que me permite escrever coisas assim:

svn st | c2 | xargs rm

15
As funções bash são geralmente mais úteis. Eu fiz o seguinte: function c() { awk "{print \$$1}" } Então você pode fazer: svn st | c 2 | xargs rm
— Aissen

8
Mas então eu preciso digitar um espaço extra. Isso é muito cansativo :)
— StackedCrooked

16

Experimente o zsh. Ele suporta alias de sufixo, para que você possa definir o X no seu .zshrc como

alias -g X="| cut -d' ' -f2"

então você pode fazer:

cat file X

Você pode dar um passo adiante e defini-lo para a enésima coluna:

alias -g X2="| cut -d' ' -f2"
alias -g X1="| cut -d' ' -f1"
alias -g X3="| cut -d' ' -f3"

que produzirá a enésima coluna do arquivo "file". Você também pode fazer isso para saída grep ou menos saídas. Isso é muito útil e um recurso matador do zsh.

Você pode dar um passo adiante e definir D como:

alias -g D="|xargs rm"

Agora você pode digitar:

cat file X1 D

para excluir todos os arquivos mencionados na primeira coluna do arquivo "arquivo".

Se você conhece o bash, o zsh não muda muito, exceto por alguns novos recursos.

HTH Chris


ahh, vejo que você atualizou sua resposta enquanto eu estava digitando meu comentário acima :) Você pode especificar dinamicamente qual coluna obter ou precisaria de n-linhas no meu .zshrc (não que isso seja importante, apenas curioso)
— Sv1

Eu editei minha postagem ainda mais e defini um sufixo "D" para excluir arquivos. Tanto quanto eu sei, você terá que adicionar uma linha para cada sufixo.
— Chris

Ou torne-o o primeiro parâmetro do comando X. Nada disso requer zsh ou alias, exceto, talvez, pela idéia peculiar de colocar um pipe em um alias.
— Tripleee 6/09/11

1
Eu não entendo porque todos vocês parecem gostar da cutopção. Simplesmente não funciona na minha máquina usando a saída de svn st. Tente svn st | cut -d' ' -f2apenas ver o que acontece.
— Lynch

@ SV1 você pode escrever um loop para definir os aliases, já que o alias é apenas um comando.
— driftcatcher

8

Como você não está familiarizado com os scripts, aqui está um exemplo.

#!/bin/sh
# usage: svn st | x 2 | xargs rm
col=$1
shift
awk -v col="$col" '{print $col}' "${@--}"

Se você salvar isso ~/bin/xe garantir que ~/binestá no seu PATH(agora isso é algo que você pode e deve colocar no seu .bashrc), você possui o comando mais curto possível para extrair geralmente a coluna n; x n.

O script deve executar uma verificação de erro e salvá-lo adequadamente, se invocado com um argumento não numérico ou com um número incorreto de argumentos, etc; mas a expansão dessa versão essencial básica estará na unidade 102.

Talvez você queira estender o script para permitir um delimitador de coluna diferente. O awk, por padrão, analisa a entrada em campos no espaço em branco; Para usar um delimitador diferente, use -F ':'where :é o novo delimitador. Implementar isso como uma opção para o script o torna um pouco mais longo, então estou deixando isso como um exercício para o leitor.


Uso

Dado um arquivo file:

1 2 3
4 5 6

Você pode passá-lo via stdin (usando um inútilcat apenas como espaço reservado para algo mais útil);

$ cat file | sh script.sh 2
2
5

Ou forneça isso como argumento para o script:

$ sh script.sh 2 file
2
5

Aqui, sh script.shestá assumindo que o script é salvo como script.shno diretório atual; se você o salvar com um nome mais útil em algum lugar do seu PATHe o marcar como executável, como nas instruções acima, obviamente use o nome útil (e não sh).


Boa ideia, mas não funciona muito bem para mim, como é no sh ou no bash. Isso funciona: #! / Bin / bash col = $ 1 shift awk "{print \ $$ col}"
— mgk 25/01/15

Obrigado por este comentário tardio. Atualizado com sua correção.
— tripleee

1
melhorawk -v col=$col ...
— fedorqui 'ASSIM pare de prejudicar' 01/12/15

@fedorqui Obrigado pelo seu feedback, aqui e em outros lugares! Atualizado a resposta. Agora está um pouco mais longo, portanto, se você quiser o script absolutamente mais curto possível, consulte o histórico de revisões da versão original.
— Tripleee

1
@fedorqui Muito obrigado! Adicionada uma pequena advertência ao catexemplo por razões histéricas (-:
— tripleee

5

Parece que você já tem uma solução. Para facilitar as coisas, por que não colocar seu comando em um script bash (com um nome abreviado) e simplesmente executá-lo em vez de digitar o comando 'long' toda vez?


É que não é tão "legal" na minha opinião. Por quê? Bem, eu não quero ter que inundar meu .bashrc com vários atalhos que fazem isso e aquilo, essencialmente escrevendo um meta-shell. É bastante apelativo como é. Dito isto, sua sugestão não é ruim.
— Sv1 6/09/11

2
.bashrc? Por que você iria desordenar com coisas não relacionadas ao bash. O que faço é escrever scripts individuais para cada 'conjunto' de comandos e colocá-los todos em um ~/scriptsdiretório. Em seguida, adicione o todo ~/scriptsao meu, PATHpara que eu possa chamá-los pelo nome quando precisar deles.
— Tarek Fadel

4
Então não o coloque no seu .bashrc. Crie um script em ~ / bin e verifique se ele está no seu PATH.
— tripleee

2

Se você concorda com a seleção manual da coluna, pode ser muito rápido usando pick :

svn st | pick | xargs rm

Basta ir a qualquer célula da 2ª coluna, pressionar ce depois pressionarenter


Eu experimentei a ferramenta "pick" que você referenciou e eu gosto muito. É muito bom para muitas situações em que quero imprimir colunas selecionadas, mas não quero digitar "awk '{print $ 3}'" apenas para obter a coluna desejada. Infelizmente, o nome entra em conflito com uma ferramenta "pick" diferente, que parece ser mais popular - ele pode ser instalado com "apt install pick". Então, renomeei sua ferramenta para "pickk" no meu sistema e pretendo continuar usando-a. Obrigado por postar uma referência.
— William Dye

1

Observe que esse caminho do arquivo não precisa estar na segunda coluna da saída svn st. Por exemplo, se você modificar o arquivo e modificar sua propriedade, será a terceira coluna.

Veja possíveis exemplos de saída em:

svn help st

Exemplo de saída:

 M     wc/bar.c
A  +   wc/qax.c

Sugiro cortar os 8 primeiros caracteres da seguinte maneira:

svn st | cut -c8- | while read FILE; do echo whatever with "$FILE"; done

Se você deseja ter 100% de certeza e lidar com nomes de arquivos sofisticados com espaço em branco no final, por exemplo, é necessário analisar a saída xml:

svn st --xml | grep -o 'path=".*"' | sed 's/^path="//; s/"$//'

Obviamente, você pode querer usar algum analisador XML real em vez de grep / sed.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.