Sintaxe correta para uso de awk em combinação com outro comando dentro de xargs sh –c

1

Como fazer este comando funcionar:

ls * | xargs -I {} sh -c 'echo {}; awk '{print $1}' {} | uniq'

Deve fazer o seguinte: imprimir para cada arquivo na pasta seu nome e uniq valores na primeira coluna

Ele não funciona porque o símbolo $ é reconhecido como o fim do símbolo da string, e deve haver algo a ver com aspas, eu acho.

A mensagem de erro:

awk: cmd. line:1: {print
awk: cmd. line:1:       ^ unexpected newline or end of string
    
por Ekaterina 11.05.2016 / 14:56

3 respostas

4

A segunda cota única encerra a primeira cadeia de caracteres com um único algarismo 'echo {}; awk ' . Em seguida, {print $1} não é citada e, em seguida, há outra sequência com aspas simples ' {} | uniq' . Isso deve ficar claro em qualquer editor com realce de sintaxe; também fica claro se você olhar o destaque da sintaxe em sua pergunta.

Aqui, a abordagem mais simples seria evitar completamente as citações aninhadas. Passe o script awk como um argumento para sh .

xargs -I {} sh -c 'echo "$1"; awk "$0"' '{print $1}' {} | uniq'

(Eu também substituí o {} dentro do script pelo argumento correspondente a sh . Nunca use {} dentro de um script: ele seria analisado como sintaxe do shell, não como um nome de arquivo, por isso iria falhar catastroficamente em qualquer nome de arquivo contendo caracteres especiais de shell.)

Para incluir efetivamente uma aspa simples em um literal com aspas simples, use '\'' (formalmente, isso termina o literal com aspas simples e, em seguida, adiciona uma aspa simples que é interpretada literalmente devido ao backflash anterior e inicia outra literal citado, mas o efeito é o mesmo).

xargs -I {} sh -c 'echo {}; awk '\''{print $1}'\'' {} | uniq'

Como alternativa, use aspas simples em um nível e aspas duplas no outro nível, mas fica mais complicado.

(Eu suponho que seus comandos sem sentido, como ls * , sejam apenas um exemplo extremamente simplificado.)

    
por 12.05.2016 / 00:53
2

Você não precisa de xargs .

Como li em outro lugar neste site (desculpe, não consigo me lembrar de onde) de um usuário principal:

Yes, xargs is a cool toy. No, you don't need to use it.

Isto:

ls * | xargs -I {} sh -c 'echo {}; awk '{print $1}' {} | uniq'

Pode ser totalmente substituído por isso:

for f in *; do echo "$f"; awk '{print $1}' "$f" | uniq; done

Isso proporciona uma melhoria significativa de segurança em relação à sua versão anterior, para não mencionar a legibilidade e a funcionalidade real. (Claro que a primeira versão não funciona de forma alguma, devido à tentativa de aninhamento de aspas simples, o que é impossível ** .)

Mesmo se você corrigir a citação de sua versão, no entanto, estará se abrindo. Preenchendo o nome de um nome de arquivo arbitrário em um comando de shell dentro de -c , você está efetivamente executando eval nesse nome de arquivo, e assim existem inúmeras façanhas que podem ser feitas simplesmente criando nomes de arquivos específicos. Por exemplo, touch ';rm -rf "$HOME" #' resultaria na remoção do seu diretório pessoal.

Para um tratamento totalmente garantido de nomes de arquivos ímpares, incluindo nomes de arquivos que podem ser interpretados como sinalizadores de opção awk , use o seguinte:

for f in *; do printf '%s\n' "$f"; awk '{print $1}' < "$f" | uniq; done
    
por 12.05.2016 / 03:52
0

Você tem dois problemas principais:

  1. o encanamento ls * em xargs está simplesmente errado. Ele irá quebrar se qualquer um dos nomes de arquivos contiver espaços, novas linhas, caracteres de globalização ou (dependendo do que você está executando com xargs ) se eles começarem com - .

    use find ... -print0 | xargs -0 .

  2. Citações aninhadas. Como @Gilles menciona em sua resposta, existem maneiras de fazer isso corretamente, mas é muito fácil de se perder e ficar confuso se você tiver vários níveis de citações aninhadas - e mesmo que tenha sucesso, você provavelmente tem código que você não pode (facilmente) ler ou entender amanhã, muito menos em seis meses.

É muito mais fácil apenas escrever um script que faz o que você quer e executá-lo com xargs.

Se o script funcionar sozinho com vários argumentos de nome de arquivo, ele funcionará com xargs - e sem ter que usar -I {} (o que implica -L 1 . A versão de xargs do FreeBSD também possui uma opção -J evita esse problema).

Por exemplo, myscript.sh :

#! /bin/sh

for f in "$@" ; do
    echo "$f"
    awk '{ print $1 }' -- "$f" | uniq
done

(a maioria das versões de awk que eu tentei entender -- significa parar de processar args de opção. original-awk , que é o mesmo que awk de freebsd não. Se o seu awk não, apenas remova-o da linha de comando awk )

e execute-o como:

./myscript.sh *

Observe que * corresponderá aos subdiretórios e aos arquivos.

ou assim:

find . -maxdepth 1 -type f -print0 | xargs -0r /path/to/myscript.sh

ou

find . -maxdepth 1 -type f -exec /path/to/myscript.sh {} +

Estes dois irão processar apenas arquivos regulares no diretório atual.

Se os arquivos de entrada não forem pré-classificados, use sort -u em vez de uniq .

    
por 12.05.2016 / 03:32