Comando Bash awk com aspas

8

Eu tenho tentado encontrar a resposta para esta pergunta há algum tempo. Estou escrevendo um script rápido para executar um comando com base na saída do awk.

ID_minimum=1000
for f in /etc/passwd;
do 
    awk -F: -vID=$ID_minimum '$3>=1000 && $1!="nfsnobody" { print "xfs_quota -x -c 'limit bsoft=5g bhard=6g $1' /home "}' $f;       
done

Os problemas são que o -cargumento usa um comando entre aspas simples e não consigo descobrir como escapar disso adequadamente e que $1não se expande para o nome de usuário.

Basicamente, estou apenas tentando obtê-lo:

xfs_quota -x -c 'limit bsoft=5g bhard=6g userone' /home
xfs_quota -x -c 'limit bsoft=5g bhard=6g usertwo' /home

etc ...

ZCT
fonte
2
Possível duplicata de Como escapar de aspas no shell?
Jordanm # 13/19
2
Seu loop apenas itera uma vez.
Jordanm # 13/19
@jordanm Acho que isso não se aplica aawk
jesse_b 13/09/19
2
@Jesse_b, seu problema de citação é o shell citar, realmente não tem nada a ver com o awk. Ele precisa escapar de aspas simples aninhadas.
Jordanm # 13/19

Respostas:

13

Para executar o comando xfs_quota -x -c 'limit bsoft=5g bhard=6g USER' /homepara cada um USERcujo UID seja pelo menos $ID_minimum, considere analisar primeiro esses usuários e, em seguida, execute o comando, em vez de tentar criar uma sequência que represente o comando que você deseja executar.

Se você criar a cadeia de comando, você precisaria eval. É complicado e fácil de errar. É melhor apenas obter uma lista de nomes de usuários e depois executar o comando.

getent passwd |
awk -F: -v min="${ID_minimum:-1000}" '$3 >= min && $1 != "nfsnobody" { print $1 }' |
while IFS= read -r user; do
    xfs_quota -x -c "limit bsoft=5g bhard=6g $user" /home
done

Observe que não há necessidade real de aspas simples ao redor do argumento a seguir -c. Aqui eu uso aspas duplas porque quero que o shell expanda a $uservariável que contém valores extraídos por awk.

Eu uso ${ID_minimum:-1000}ao dar o valor para a minvariável no awkcomando. Isso será expandido para o valor de $ID_minimumou 1000se essa variável estiver vazia ou não definida.


Se você realmente quisesse, poderia fazer o loop acima imprimir os comandos em vez de executá-los:

getent passwd |
awk -F: -v min="${ID_minimum:-1000}" '$3 >= min && $1 != "nfsnobody" { print $1 }' |
while IFS= read -r user; do
    printf 'xfs_quota -x -c "limit bsoft=5g bhard=6g %s" /home\n' "$user"
done

Observe novamente que o uso de aspas duplas na cadeia de comando gerada (em vez de aspas simples) não confundiria um shell de forma alguma se você executasse os comandos gerados usando evalou através de outros meios. Se isso lhe incomoda, basta trocar as aspas simples e duplas no primeiro argumento para printfcima.

Kusalananda
fonte
11
a única resposta para usar corretamente o getent em vez de analisar / etc / passwd.
cas
11
@cas O macOS é um Unix sem getent(na área de trabalho, de qualquer maneira), e a pergunta não está marcada como "linux".
TheDudeAbides 14/09/19
2
@TheDudeAbides Supondo que o usuário tenha escolhido o UID 1000 porque é o limite entre as contas de serviço do sistema e as contas de usuário em seu sistema, podemos deduzir que esse usuário não está executando no macOS (a primeira conta de usuário é 501). Além disso, o uso de utilitários XFS no macOS é bastante incomum, pois o XFS não é realmente suportado pela Apple. Além disso, /homenão é realmente usado no macOS (está lá, mas geralmente está vazio).
Kusalananda
@Kusalananda Ponto tomado. Eu estava cego para a parte XFS.
TheDudeAbides 14/09/19
11
O @TheDudeAbides getent não é apenas para Linux. também está no netbsd, freebsd e solaris.
cas
6
for f in /etc/passwd;

Isso é um pouco bobo, já que não há realmente nenhum loop com apenas um valor.

Mas o problema parece estar imprimindo aspas simples do awk. Você pode escapar deles no shell, mas também pode usar escapes de barra invertida no awk para imprimi-los. é o caractere com valor numérico OOO (em octal ), assim é . Portanto, essa seria uma maneira de fazer isso:\OOO\047'

awk -F: -vID=$ID_minimum '$3>=1000 && $1!="nfsnobody" {
    printf "xfs_quota -x -c \047limit bsoft=5g bhard=6g %s\047 /home\n", $1}' /etc/passwd

Você pode usar o escape semelhante em hexadecimal, \x27mas ele pode ser mal interpretado em algumas implementações se o caractere a seguir for um dígito hexadecimal válido. (E é claro que eu assumi ASCII ou um conjunto de caracteres compatível com ASCII, por exemplo, UTF-8.)

ilkkachu
fonte
Observe que não há problema aqui, pois lnão é um dígito hexadecimal, mas "\x27df\n"seria tratado como "\x27" "df"no busybox awk ou mawk, mas como "\xdf"( 0x27dfconvertido para char de 8 bits) no original awk e gawk (essa é a razão pela qual o POSIX não especifica \xHH). Octals ( \047) não têm o mesmo problema e são POSIX. Em qualquer caso, isso pressupõe um sistema ASCII (uma suposição razoável nos dias de hoje).
Stéphane Chazelas
6

Use a -f -opção awk para obter o script stdin e um documento here:

awk -F: -v "ID=$ID_minimum" -f - <<'EOT' /etc/passwd
$3>=1000 && $1!="nfsnobody" {
    print "xfs_quota -x -c 'limit bsoft=5g bhard=6g "$1"' /home "
}
EOT
mosvy
fonte
2

Isso foi feito.

awk -F: -vID=$ID_minimum '$3>=1000 && $1!="nfsnobody" { print "xfs_quota -x -c '"'"'limit bsoft=5g bhard=6g ''"$1"'''"'"' /home "}' /etc/passwd
ZCT
fonte
11
@ Jessie_b, sim, você não pode colocar aspas simples dentro de uma string de aspas simples, então você deve terminar primeiro e depois citar a aspas simples que deseja inserir. Não importa se você faz '\''ou '"'"'. Ambos funcionam, ambos parecem irritantes.
ilkkachu 13/09/19
@OP, espero que você tenha visto o comentário de jordanm sobre o seu loop ser desnecessário. Seu loop é executado apenas uma vez, portanto, não é diferente de simplesmente executar o mesmo comando awk fora de um loop.
jesse_b 13/09/19
1

É um alojamento horrível, mas é rápido e fácil ...

awk -F: -vQ="'" -vID=$ID_minimum '$3>=1000 && $1!="nfsnobody" { print "xfs_quota -x -c " Q "limit bsoft=5g bhard=6g $1" Q " /home "}' $f;       
Grump
fonte
1

Isso me parece uma oportunidade ideal para empregar alguns xargs(ou GNU Parallel ):

getent passwd \
  | awk -F: '$3>=1000 && $1!="nfsnobody" {print $1}' \
  | xargs -I{} \
      echo xfs_quota -x -c \"limit bsoft=5g bhard=6g {}\" /home

# output:
# xfs_quota -x -c "limit bsoft=5g bhard=6g userone" /home
# xfs_quota -x -c "limit bsoft=5g bhard=6g usertwo" /home

A vantagem de usar xargsou parallelé que você pode simplesmente remover oecho quando estiver pronto para executar o comando de verdade (possivelmente substituindo-o por sudo, se necessário).

Você também pode usar as opções desses utilitários -p/ --interactive(o último é somente GNU) ou --dry-run( parallelapenas), para obter confirmação antes de executar cada um ou apenas para ver o que seria executado antes de executá-lo.

O método geral usado acima deve funcionar na maioria dos Unixes e não requer xargsopções específicas do GNU . As aspas duplas fazer necessidade de ser "escapado" para que eles aparecem literalmente na saída. Note que a "cadeia de substituição," {}, no xargs -I{}pode ser qualquer coisa que você prefere, e -Iimplica -L1(executar um comando por linha de entrada ao invés de lotes-los).

GNU Parallel não requer a -Iopção ( {}é a cadeia de substituição padrão), e dá-lhe a gratificação instantânea de correr muitos postos de trabalho em paralelo, mesmo se você não quer incomodar a aprender sobre qualquer de suas outras características .

Como uma observação lateral, nem tenho certeza se xfs_quotaa -copção deve ser usada assim, embora eu não tenha sistemas de arquivos XFS à mão para testar. Você pode nem ter precisado lidar com uma string citada em primeiro lugar (a menos que espere nomes de usuários com espaços em branco, o que eu acho que é possível), pois parece que você pode dar várias -copções na mesma linha de comando, de acordo com para a página de manual incluída em xfsprogs4.5.algo.

TheDudeAbides
fonte
0

Com o GNU Parallel, você pode:

getent passwd |
  parallel --colsep : -q xfs_quota -x -c \
    'limit bsoft=5g bhard=6g {=1 $_ eq "nfsnobody" and skip(); $arg[3] <= 1000 and skip();  =}' /home

Explicação:

--colsep :Dividir em:
-qnão divida o comando em espaços (mantenha o '...' como uma única sequência)
{=1 ... =}Avalie esta expressão perl no primeiro argumento da linha
$_ eq "nfsnobody" and skip();Se o valor == nfsnobody: skip
$arg[3] <= 1000 and skip();Se argument3 <= 1000: skip

Ole Tange
fonte