Commande Bash awk avec guillemets

8

J'essaie de trouver la réponse à cette question depuis un certain temps. J'écris un script rapide pour exécuter une commande basée sur la sortie de 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

Les problèmes sont que l' -cargument prend une commande entre guillemets simples et je ne peux pas comprendre comment échapper correctement cela et aussi qui $1ne se développe pas dans le nom d'utilisateur.

Essentiellement, j'essaie simplement de le faire sortir:

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

etc...

Z CT
la source
2
Votre boucle ne réitère qu'une seule fois.
jordanm
@jordanm Je ne pense pas que cela s'applique àawk
jesse_b
2
@Jesse_b son problème de citation est la citation de shell, n'a vraiment rien à voir avec awk. Il doit échapper aux guillemets simples imbriqués.
jordanm

Réponses:

13

Pour exécuter la commande xfs_quota -x -c 'limit bsoft=5g bhard=6g USER' /homepour chacun USERdont l'UID est au moins $ID_minimum, envisagez d'abord d'analyser ces utilisateurs, puis exécutez réellement la commande, plutôt que d'essayer de créer une chaîne représentant la commande que vous souhaitez exécuter.

Si vous créez la chaîne de commande, vous devrez le faire eval. C'est délicat et facile de se tromper. Il est préférable d'obtenir simplement une liste de noms d'utilisateurs, puis d'exécuter la commande.

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

Notez qu'il n'y a aucun besoin réel de guillemets simples autour de l'argument après -c. Ici, j'utilise des guillemets parce que je veux que le shell développe la $uservariable qui contient les valeurs extraites par awk.

J'utilise ${ID_minimum:-1000}pour donner la valeur à la minvariable dans la awkcommande. Cela se développera à la valeur de $ID_minimum, ou à 1000si cette variable est vide ou non définie.


Si vous le vouliez vraiment , vous pourriez faire en sorte que la boucle ci-dessus imprime les commandes au lieu de les exécuter:

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

Notez à nouveau que l'utilisation de guillemets doubles dans la chaîne de commande émise (au lieu de guillemets simples) ne perturberait en aucun cas un shell si vous exécutiez les commandes générées à l'aide evalou par d'autres moyens. Si cela vous dérange, permutez simplement les guillemets simples et doubles dans le premier argument printfci-dessus.

Kusalananda
la source
1
la seule réponse pour utiliser correctement getent plutôt que d'analyser / etc / passwd.
cas
1
@cas macOS est un Unix sans getent(sur le bureau, de toute façon), et la question n'est pas étiquetée "linux".
TheDudeAbides
2
@TheDudeAbides En supposant que l'utilisateur a choisi l'UID 1000 car c'est la coupure entre les comptes de service système et les comptes d'utilisateur sur leur système, nous pouvons déduire que cet utilisateur ne fonctionne pas sur macOS (le premier compte d'utilisateur est 501). En outre, l'utilisation des utilitaires XFS sur macOS est assez rare car XFS n'est pas vraiment pris en charge par Apple. De plus, /homen'est pas vraiment utilisé sur macOS (il est là, mais généralement vide).
Kusalananda
@Kusalananda Point pris. J'étais aveugle à la partie XFS.
TheDudeAbides
1
@TheDudeAbides getent n'est pas uniquement Linux. c'est aussi sur netbsd, freebsd et solaris au moins.
cas
6
for f in /etc/passwd;

C'est un peu idiot car il n'y a vraiment pas de boucle avec une seule valeur.

Mais le problème semble être d'imprimer des guillemets simples à partir de awk. Vous pouvez les échapper dans le shell, mais vous pouvez également utiliser des barres obliques inversées dans awk pour les imprimer. est le caractère avec la valeur numérique OOO (en octal ), donc est . Ce serait donc une façon de procéder:\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

Vous pouvez utiliser l'échappement similaire en hexadécimal, \x27mais il peut être mal interprété dans certaines implémentations si le caractère suivant est un chiffre hexadécimal valide. (Et bien sûr, j'ai supposé ASCII ou un jeu de caractères compatible ASCII, par exemple UTF-8.)

ilkkachu
la source
Notez que c'est bien ici car ce ln'est pas un chiffre hexadécimal, mais "\x27df\n"serait traité comme "\x27" "df"dans awkbox ou mawk de busybox mais comme "\xdf"( 0x27dfconverti en caractère 8 bits) dans awk et gawk d'origine (c'est la raison pour laquelle POSIX ne le spécifie pas \xHH). Les octales ( \047) n'ont pas le même problème et sont POSIX. Dans tous les cas, cela suppose un système ASCII (une hypothèse raisonnable de nos jours).
Stéphane Chazelas
6

Utilisez l' -f -option awk pour prendre le script de stdin et un document ici:

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
la source
2

Ça l'a fait.

awk -F: -vID=$ID_minimum '$3>=1000 && $1!="nfsnobody" { print "xfs_quota -x -c '"'"'limit bsoft=5g bhard=6g ''"$1"'''"'"' /home "}' /etc/passwd
Z CT
la source
1
@Jesse_b, oui, vous ne pouvez pas mettre de guillemets simples dans une chaîne entre guillemets simples, vous devez donc la terminer d'abord, puis citer la guillemet simple que vous souhaitez insérer. Peu importe que vous le fassiez '\''ou '"'"'. Les deux fonctionnent, les deux semblent ennuyeux.
ilkkachu
@OP, j'espère que vous avez vu le commentaire de jordanm sur votre boucle inutile. Votre boucle n'est exécutée qu'une seule fois, ce n'est donc pas différent que d'exécuter simplement la même commande awk en dehors d'une boucle.
jesse_b
1

C'est une horrible loge, mais c'est rapide et facile ...

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
la source
1

Cela me semble être une opportunité idéale pour vous en employer 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

L'avantage d'utiliser xargsou parallelest que vous pouvez simplement supprimer leecho lorsque vous êtes prêt à exécuter la commande pour de vrai (éventuellement en la remplaçant sudosi nécessaire).

Vous pouvez également utiliser les options de ces utilitaires -p/ --interactive(ce dernier est uniquement GNU) ou --dry-run( paralleluniquement), pour obtenir une confirmation avant d'exécuter chacun, ou simplement pour voir ce qui fonctionnerait, avant de l'exécuter.

La méthode générale utilisée ci-dessus devrait fonctionner sur la plupart des Unix et ne nécessite aucune xargsoption spécifique à GNU . Les guillemets doubles ne doivent être « échappé » de sorte qu'ils apparaissent littéralement dans la sortie. Notez que la "chaîne de remplacement" {}dans xargs -I{}peut être tout ce que vous préférez et -Iimplique -L1(exécutez une commande par ligne d'entrée plutôt que de les regrouper).

GNU Parallel ne nécessite pas l' -Ioption ( {}c'est la chaîne de remplacement par défaut), et vous donne le bonus instantané d'exécuter de nombreux travaux en parallèle, même si vous ne voulez pas prendre la peine de vous renseigner sur l' une de ses autres fonctionnalités .

En remarque, je ne suis même pas sûr si xfs_quotal' -coption est censée être utilisée comme ça, bien que je n'ai pas de systèmes de fichiers XFS à portée de main. Vous n'avez peut-être même pas eu besoin de traiter une chaîne entre guillemets en premier lieu (sauf si vous vous attendez à des noms d'utilisateur avec des espaces, ce qui, je suppose, est possible), car il semble que vous pouvez donner plusieurs -coptions sur la même ligne de commande, selon à la page de manuel incluse avec xfsprogs4.5.quelque chose.

TheDudeAbides
la source
0

Avec GNU Parallel, vous pouvez faire:

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

Explication:

--colsep :Fractionner sur:
-qne pas fractionner la commande sur les espaces (garder le '...' comme une seule chaîne)
{=1 ... =}Évaluer cette expression perl sur le premier argument de la ligne
$_ eq "nfsnobody" and skip();Si la valeur == nfsnobody: sauter
$arg[3] <= 1000 and skip();Si argument3 <= 1000: sauter

Ole Tange
la source