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 :)

Sv1
fonte
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\+//'

porges
fonte
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.

Rick Sladkey
fonte
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
StackedCrooked
fonte
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

Chris
fonte
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).

triplo
fonte
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?

Tarek Fadel
fonte
É 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

Bernardo Rufino
fonte
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.

Michał Šrajer
fonte