Comando bash awk con comillas


8

He estado tratando de encontrar la respuesta a esta pregunta por un tiempo. Estoy escribiendo un script rápido para ejecutar un comando basado en la salida 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

Los problemas son que el -cargumento toma un comando entre comillas simples y no puedo encontrar la manera de escapar de eso y eso $1no se expande al nombre de usuario.

Esencialmente solo estoy tratando de que salga:

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

etc ...



2
Su ciclo solo se repite una vez.
jordanm

@jordanm No creo que eso se aplique aawk
jesse_b

2
@Jesse_b su problema de citas es citar shell, realmente no tiene nada que ver con awk. Necesita escapar de comillas simples anidadas.
jordanm

Respuestas:


13

Para ejecutar el comando xfs_quota -x -c 'limit bsoft=5g bhard=6g USER' /homepara cada uno USERcuyo UID es al menos $ID_minimum, considere analizar primero a esos usuarios y luego realmente ejecutar el comando, en lugar de intentar crear una cadena que represente el comando que desea ejecutar.

Si crea la cadena de comando, debería evalhacerlo. Esto es complicado y fácil de equivocarse. Es mejor obtener una lista de nombres de usuario y luego ejecutar el 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

Tenga en cuenta que no hay necesidad real de comillas simples alrededor del argumento posterior -c. Aquí uso comillas dobles porque quiero que el shell expanda el$user variable que contiene los valores extraídos por awk.

Lo uso ${ID_minimum:-1000}cuando le doy el valor a la minvariable en el awkcomando. Esto se expandirá al valor de $ID_minimum, o a 1000si esa variable está vacía o no está establecida.


Si realmente quisiera, podría hacer que el bucle anterior imprima los comandos en lugar de ejecutarlos:

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

Tenga en cuenta nuevamente que el uso de comillas dobles en la cadena de comando que se genera (en lugar de comillas simples) no confundiría a un shell de ninguna manera si tuviera que ejecutar los comandos generados utilizando evalo por algún otro medio. Si te molesta, solo cambia las comillas simples y dobles en el primer argumento printfanterior.


1
la única respuesta para usar correctamente getent en lugar de analizar / etc / passwd.
cas

1
@cas macOS es un Unix sin getent(en el escritorio, de todos modos), y la pregunta no está etiquetada como "Linux".
TheDudeAbides

2
@TheDudeAbides Suponiendo que el usuario eligió UID 1000 porque es el límite entre las cuentas de servicio del sistema y las cuentas de usuario en su sistema, podemos inferir que este usuario no se está ejecutando en macOS (la primera cuenta de usuario es 501). Además, el uso de las utilidades XFS en macOS es bastante raro, ya que XFS no es realmente compatible con Apple. Además, /homeno se usa realmente en macOS (está allí, pero generalmente está vacío).
Kusalananda

@Kusalananda Punto tomado. Estaba ciego a la parte de XFS.
TheDudeAbides

1
@TheDudeAbides getent no es solo para Linux. también está en netbsd, freebsd y solaris al menos.
cas

6
for f in /etc/passwd;

Esto es un poco tonto ya que realmente no hay bucle con un solo valor.

Pero el problema parece estar imprimiendo comillas simples de awk. Podrías escapar de ellos en el caparazón, pero también podrías usar la barra invertida dentro de awk para imprimirlos. es el carácter con valor numérico OOO (en octal ), también lo es . Entonces esta sería una forma de hacerlo:\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

Podría usar el escape similar en hexadecimal, \x27pero puede malinterpretarse en algunas implementaciones si el siguiente carácter es un dígito hexadecimal válido. (Y, por supuesto, asumí ASCII o un conjunto de caracteres compatible con ASCII, por ejemplo, UTF-8).


Tenga en cuenta que está bien aquí, ya lque no es un dígito hexadecimal, pero se "\x27df\n"trataría como "\x27" "df"en busybox awk o mawk, pero como "\xdf"( conversión 0x27dfa char de 8 bits) en el awk y gawk original (esa es la razón por la cual POSIX no especifica \xHH). Los Octals ( \047) no tienen el mismo problema y son POSIX. En cualquier caso, eso supone un sistema ASCII (una suposición razonable en estos días).
Stéphane Chazelas

6

Use la -f -opción awk para tomar el script de stdin y un documento aquí:

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

Esto lo hizo.

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í, no puede poner comillas simples dentro de una cadena entre comillas simples, por lo que primero debe terminarla y luego citar la comilla simple que desea insertar. No importa si lo haces '\''o no '"'"'. Ambos funcionan, ambos parecen molestos.
ilkkachu

@OP, espero que hayas visto innecesario el comentario de jordanm sobre tu bucle. Su bucle solo se ejecuta una vez, por lo que no es diferente de simplemente ejecutar el mismo comando awk fuera de un bucle.
jesse_b

1

Es un cuerpo horrible, pero es rápido y 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;       

1

Esto me parece una oportunidad ideal para emplear algunos xargs(o GNU Paralelo ):

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

La ventaja de usar xargso paralleles que simplemente puede eliminar elecho cuando esté listo para ejecutar el comando de verdad (posiblemente reemplazándolo con sudo, si es necesario).

También puede usar las opciones de estas utilidades -p/ --interactive(esta última es solo GNU) o --dry-run( parallelsolo), para obtener confirmación antes de ejecutar cada una, o simplemente para ver qué se ejecutaría, antes de ejecutarla.

El método general utilizado anteriormente debería funcionar en la mayoría de los Unix, y no requiere xargsopciones específicas de GNU . Las comillas dobles hacen necesidad de ser "escaparon" de modo que aparezcan literalmente en la salida. Tenga en cuenta que la "cadena de reemplazo" {}, en xargs -I{}puede ser cualquier cosa que prefiera, y-I implica-L1 (ejecutar un comando por línea de entrada en lugar de procesamiento por lotes para arriba).

Paralelo GNU no requiere la -Iopción ( {}es la cadena de sustitución por defecto), y le da el bono instantáneo de funcionamiento de muchos puestos de trabajo en paralelo, incluso si usted no quiere molestarse en aprender acerca de cualquier de sus otras características .

Como nota al margen, ni siquiera estoy seguro de si xfs_quota's -cse supone posibilidad de ser usada como este, aunque no tengo ni sistemas de archivos XFS útil para la prueba. Es posible que ni siquiera haya necesitado lidiar con una cadena entre comillas en primer lugar (a menos que espere nombres de usuario con espacios en blanco, lo que supongo que es posible), ya que parece que puede dar múltiples-c opciones en la misma línea de comando, de acuerdo con a la página de manual incluida con xfsprogs4.5.algo.


0

Con GNU Parallel puedes hacer:

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

Explicación:

--colsep :Dividir en:
-qno divida el comando en espacios (mantenga el '...' como una sola cadena)
{=1 ... =}Evalúe esta expresión perl en el primer argumento de la línea
$_ eq "nfsnobody" and skip();Si el valor == nfsnobody: omita
$arg[3] <= 1000 and skip();If argumento3 <= 1000: omita

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.