Comando Bash awk tra virgolette


8

Ho cercato di trovare la risposta a questa domanda per un po '. Sto scrivendo uno script veloce per eseguire un comando basato sull'output di 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

I problemi sono che l' -cargomento prende un comando tra virgolette singole e non riesco a capire come sfuggire correttamente a quello e anche che $1non si espande nel nome utente.

In sostanza sto solo cercando di farlo uscire:

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

eccetera...



2
Il tuo ciclo viene ripetuto una sola volta.
Giordania,

@jordanm Non credo che si applichi aawk
jesse_b

2
@Jesse_b il suo problema di quotazione è la quotazione di shell, non ha davvero nulla a che fare con awk. Deve sfuggire alle virgolette singole nidificate.
Giordania,

Risposte:


13

Per eseguire il comando xfs_quota -x -c 'limit bsoft=5g bhard=6g USER' /homeper ciascuno il USERcui UID è almeno $ID_minimum, prendere in considerazione prima l'analisi di quegli utenti e quindi effettivamente eseguire il comando, piuttosto che provare a creare una stringa che rappresenta il comando che si desidera eseguire.

Se crei la stringa di comando, dovresti evalfarlo. È facile e sbagliato sbagliare. È meglio ottenere un elenco di nomi utente e quindi eseguire il 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

Si noti che non è necessario effettivamente virgolette singole attorno all'argomento successivo -c. Qui uso le doppie virgolette perché voglio che la shell espanda la $uservariabile che contiene i valori estratti da awk.

Uso ${ID_minimum:-1000}quando si dà il valore alla minvariabile nel awkcomando. Questo si espanderà al valore di $ID_minimum, o a 1000se quella variabile è vuota o non impostata.


Se lo volessi davvero , potresti fare in modo che il ciclo sopra stampi i comandi invece di eseguirli:

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

Notare ancora che l'uso di virgolette doppie nella stringa di comando emessa (anziché virgolette singole) non confonderebbe in alcun modo una shell se si dovessero eseguire i comandi generati usando evalo attraverso altri mezzi. Se ti dà fastidio, scambia le virgolette singole e doppie nel primo argomento in printfalto.


1
l'unica risposta per usare correttamente getent piuttosto che analizzare / etc / passwd.
Cas

1
@cas macOS è un Unix senza getent(sul desktop, comunque), e la domanda non è taggata "linux".
TheDudeAbides

2
@TheDudeAbides Supponendo che l'utente abbia scelto UID 1000 perché è il limite tra gli account del servizio di sistema e gli account utente sul proprio sistema, possiamo dedurre che questo utente non è in esecuzione su macOS (il primo account utente è 501). Inoltre, l'uso delle utility XFS su macOS è abbastanza raro visto che XFS non è realmente supportato da Apple. Inoltre, /homenon è realmente utilizzato su macOS (è lì, ma di solito vuoto).
Kusalananda

@Kusalananda Punto preso. Ero cieco alla parte XFS.
TheDudeAbides

1
@TheDudeAbides getent non è solo linux. è almeno su netbsd, freebsd e solaris.
CAS

6
for f in /etc/passwd;

Questo è un po 'sciocco in quanto non esiste un loop con un solo valore.

Ma il problema sembra essere la stampa di virgolette singole da awk. Potresti sfuggirli nella shell, ma puoi anche usare backslash-escape in awk per stamparli. è il personaggio con valore numerico OOO (in ottale ), quindi lo è . Quindi questo sarebbe un modo per farlo:\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

È possibile utilizzare la escape simile in esadecimale, \x27ma può essere interpretata erroneamente in alcune implementazioni se il seguente carattere è una cifra esadecimale valida. (E ovviamente ho assunto ASCII o un set di caratteri compatibile ASCII, ad esempio UTF-8.)


Si noti che qui non va bene dato che lnon è una cifra esadecimale, ma "\x27df\n"sarebbe trattato come "\x27" "df"in awk o mawk di busybox ma come "\xdf"( 0x27dfcast a 8 bit char) nell'originale awk e gawk (questo è il motivo per cui POSIX non specifica \xHH). Gli ottali ( \047) non hanno lo stesso problema e sono POSIX. In ogni caso, ciò presuppone un sistema ASCII (un presupposto ragionevole in questi giorni).
Stéphane Chazelas,

6

Usa l' -f -opzione per awk per prendere lo script da stdin e un documento qui:

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

2

Questo l'ha fatto.

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

1
@Jesse_b, sì, non puoi inserire virgolette singole all'interno di una stringa a virgoletta singola, quindi devi prima terminarla, quindi citare la virgoletta singola che desideri inserire. Non importa se lo fai '\''o '"'"'. Entrambi funzionano, entrambi sembrano fastidiosi.
ilkkachu,

@OP, spero che tu abbia visto il commento di jordanm sul tuo loop non necessario. Il tuo ciclo viene eseguito solo una volta, quindi non è diverso dal semplice esecuzione dello stesso comando awk al di fuori di un ciclo.
jesse_b,

1

È un espediente terribile, ma è facile e veloce ...

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;       

1

Questa mi sembra un'opportunità ideale per impiegarti un po ' xargs(o 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

Il vantaggio di usare xargso parallelè che puoi semplicemente rimuovere ilecho quando sei pronto per eseguire il comando per davvero (eventualmente sostituendolo con sudo, se necessario).

Puoi anche usare le opzioni -p/ --interactive((quest'ultima è solo GNU) o --dry-run( parallelsolo) di queste utility , per ottenere la conferma prima di eseguirle o solo per vedere cosa verrebbe eseguito prima di eseguirlo.

Il metodo generale usato sopra dovrebbe funzionare sulla maggior parte degli Unix e non richiede xargsopzioni specifiche per GNU . Le doppie virgolette fanno necessità di essere "scappati", in modo che appaiano letteralmente in uscita. Si noti che la "stringa di sostituzione" {}in xargs -I{}può essere qualsiasi cosa tu preferisca e -Iimplica -L1(esegui un comando per riga di input anziché raggrupparli in batch).

GNU Parallel non richiede l' -Iopzione ( {}è la stringa di sostituzione predefinita) e ti dà il bonus istantaneo di eseguire molti lavori in parallelo, anche se non vuoi preoccuparti di conoscere nessuna delle sue altre funzionalità .

Come nota a margine, non sono nemmeno sicuro che xfs_quotal' -copzione debba essere usata in questo modo, anche se non ho file system XFS utili per il test. Potresti non aver nemmeno avuto bisogno di occuparti di una stringa tra virgolette in primo luogo (a meno che non ti aspetti nomi utente con spazi bianchi in essi, il che immagino sia possibile), poiché sembra che tu possa dare più -copzioni sulla stessa riga di comando, secondo alla pagina man inclusa con xfsprogs4.5.something.


0

Con GNU Parallel puoi fare:

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

Spiegazione:

--colsep :Dividi su:
-qnon dividere il comando su spazi (mantenere il '...' come una singola stringa)
{=1 ... =}Valuta questa espressione perl sul primo argomento della riga
$_ eq "nfsnobody" and skip();Se il valore == nfsnobody: skip
$arg[3] <= 1000 and skip();If argomento3 <= 1000: skip

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.