Perintah Bash awk dengan tanda kutip

8

Saya telah mencoba menemukan jawaban untuk pertanyaan ini untuk sementara waktu. Saya menulis skrip cepat untuk menjalankan perintah berdasarkan output dari 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

Masalahnya adalah bahwa -cargumen mengambil perintah dalam tanda kutip tunggal dan saya tidak tahu bagaimana cara menghindarinya dan juga yang $1tidak berkembang menjadi nama pengguna.

Pada dasarnya saya hanya mencoba untuk menghasilkan:

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

dll ...

ZCT
sumber
2
Kemungkinan duplikat dari Bagaimana cara menghindari penawaran di shell?
jordanm
2
Perulangan Anda hanya akan dilakukan sekali saja.
jordanm
@jordanm Saya tidak berpikir itu berlaku untukawk
jesse_b
2
@Jesse_b masalah kutipannya adalah mengutip shell, tidak benar-benar ada hubungannya dengan awk. Dia harus lolos dari tanda kutip tunggal bersarang.
jordanm

Jawaban:

13

Untuk menjalankan perintah xfs_quota -x -c 'limit bsoft=5g bhard=6g USER' /homeuntuk masing USER- masing yang memiliki UID setidaknya $ID_minimum, pertimbangkan untuk menguraikan pengguna tersebut terlebih dahulu dan kemudian benar - benar menjalankan perintah, daripada mencoba membuat string yang mewakili perintah yang ingin Anda jalankan.

Jika Anda membuat string perintah, Anda harus melakukannya eval. Ini salah dan mudah salah. Lebih baik hanya mendapatkan daftar nama pengguna dan kemudian menjalankan perintah.

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

Perhatikan bahwa tidak ada kebutuhan aktual untuk kutipan tunggal di sekitar argumen setelahnya -c. Di sini saya menggunakan tanda kutip ganda karena saya ingin shell untuk memperluas $uservariabel yang berisi nilai yang diekstraksi oleh awk.

Saya menggunakan ${ID_minimum:-1000}saat memberikan nilai ke minvariabel dalam awkperintah. Ini akan diperluas ke nilai $ID_minimum, atau 1000jika variabel itu kosong atau tidak disetel.


Jika Anda benar - benar ingin, Anda bisa membuat loop di atas mencetak perintah alih-alih menjalankannya:

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

Perhatikan lagi bahwa menggunakan tanda kutip ganda dalam string perintah yang dihasilkan (bukan tanda kutip tunggal) tidak akan membingungkan shell dengan cara apa pun jika Anda menjalankan perintah yang dihasilkan menggunakan evalatau melalui beberapa cara lain. Jika itu mengganggu Anda, cukup tukarkan tanda kutip tunggal dan ganda di argumen pertama ke printfatas.

Kusalananda
sumber
1
satu-satunya jawaban untuk menggunakan getent dengan benar daripada parse / etc / passwd.
cas
1
@cas macOS adalah Unix tanpa getent(tetap pada desktop), dan pertanyaannya tidak ditandai "linux."
TheDudeAbides
2
@TheDudeAbides Dengan asumsi bahwa pengguna memilih UID 1000 karena ini adalah cutoff antara akun layanan sistem dan akun pengguna pada sistem mereka, kami dapat menyimpulkan bahwa pengguna ini tidak berjalan di macOS (akun pengguna pertama adalah 501). Selain itu, menggunakan utilitas XFS pada macOS cukup jarang terlihat karena XFS tidak benar-benar didukung oleh Apple. Juga, /hometidak benar-benar digunakan pada macOS (itu ada di sana, tetapi biasanya kosong).
Kusalananda
@Kusalananda Point diambil. Saya buta pada bagian XFS.
TheDudeAbides
1
@TheDudeAbides getent bukan hanya linux. itu juga di netbsd, freebsd, dan solaris setidaknya.
cas
6
for f in /etc/passwd;

Ini agak konyol karena benar-benar tidak ada loop dengan hanya satu nilai.

Tetapi masalah ini sepertinya mencetak satu tanda kutip dari awk. Anda bisa melarikan diri dari shell, tetapi Anda juga bisa menggunakan backslash-eskt dalam awk untuk mencetaknya. adalah karakter dengan nilai numerik OOO (dalam oktal ), begitu juga . Jadi ini akan menjadi salah satu cara untuk melakukannya:\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

Anda bisa menggunakan pelarian yang sama di hex, \x27tetapi bisa salah ditafsirkan dalam beberapa implementasi jika karakter berikut ini adalah digit heksadesimal yang valid. (Dan tentu saja saya mengasumsikan ASCII atau set karakter yang kompatibel ASCII, misalnya UTF-8.)

ilkkachu
sumber
Perhatikan bahwa ini baik-baik saja di sini karena lbukan merupakan digit hex, tetapi "\x27df\n"akan diperlakukan seperti "\x27" "df"pada busybox awk atau mawk tetapi sebagai "\xdf"( 0x27dfcast ke 8 bit char) dalam awk dan gawk asli (itulah alasan mengapa POSIX tidak menentukan \xHH). Oktal ( \047) tidak memiliki masalah yang sama dan POSIX. Bagaimanapun, itu mengasumsikan sistem ASCII (asumsi yang masuk akal belakangan ini).
Stéphane Chazelas
6

Gunakan -f -opsi untuk awk untuk mengambil skrip dari stdin dan dokumen di sini:

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
sumber
2

Ini berhasil.

awk -F: -vID=$ID_minimum '$3>=1000 && $1!="nfsnobody" { print "xfs_quota -x -c '"'"'limit bsoft=5g bhard=6g ''"$1"'''"'"' /home "}' /etc/passwd
ZCT
sumber
1
@Jesse_b, ya, Anda tidak bisa memasukkan tanda kutip tunggal ke dalam string yang dikutip tunggal, jadi Anda harus mengakhirinya terlebih dahulu, dan kemudian kutip kutipan tunggal yang ingin Anda masukkan. Tidak masalah jika Anda melakukannya '\''atau '"'"'. Keduanya bekerja, keduanya terlihat menjengkelkan.
ilkkachu
@OP, semoga Anda melihat komentar jordanm tentang loop Anda menjadi tidak perlu. Loop Anda hanya dijalankan sekali sehingga tidak berbeda dari hanya menjalankan perintah awk yang sama di luar loop.
jesse_b
1

Ini tubuh yang mengerikan, tapi cepat dan mudah ...

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
sumber
1

Bagi saya ini merupakan peluang ideal untuk mempekerjakan Anda xargs(atau 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

Keuntungan menggunakan xargsatau paralleladalah Anda dapat menghapus perintahecho saat Anda siap menjalankan perintah nyata (mungkin menggantinya dengan sudo, jika perlu).

Anda juga dapat menggunakan opsi utilitas -p/ --interactive(yang terakhir ini hanya untuk GNU) atau --dry-run( parallelhanya), untuk mendapatkan konfirmasi sebelum menjalankan masing-masing, atau hanya untuk melihat apa yang akan berjalan, sebelum Anda menjalankannya.

Metode umum yang digunakan di atas harus berfungsi pada sebagian besar Unix, dan tidak memerlukan xargsopsi khusus GNU . Kutipan ganda memang perlu "diloloskan" sehingga muncul secara harfiah di output. Perhatikan bahwa "string pengganti," {}, di xargs -I{}dapat berupa apa pun yang Anda inginkan, dan -Imenyiratkan -L1(jalankan satu perintah per baris input daripada mengelompokkannya).

GNU Paralel tidak memerlukan -Ioption ( {}adalah string pengganti default), dan memberi Anda bonus instan menjalankan banyak pekerjaan secara paralel, bahkan jika Anda tidak ingin repot-repot belajar tentang apa saja dari yang lain fitur .

Sebagai catatan, aku bahkan tidak yakin apakah xfs_quota's -copsi seharusnya digunakan seperti ini, meskipun saya tidak memiliki XFS filesystem berguna untuk tes. Anda mungkin tidak perlu berurusan dengan string yang dikutip di tempat pertama (kecuali jika Anda mengharapkan nama pengguna dengan spasi putih di dalamnya, yang saya kira mungkin), karena sepertinya Anda dapat memberikan beberapa -copsi pada baris perintah yang sama, menurut ke halaman manual disertakan dengan xfsprogs4.5.something.

TheDudeAbides
sumber
0

Dengan GNU Parallel Anda dapat melakukan:

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

Penjelasan:

--colsep :Berpisah:
-qjangan pisahkan perintah pada spasi (pertahankan '...' sebagai string tunggal).
{=1 ... =}Evaluasi ekspresi perl ini pada argumen pertama baris.
$_ eq "nfsnobody" and skip();Jika nilai == nfsnobody: skip
$arg[3] <= 1000 and skip();If argument3 <= 1000: skip

Ole Tange
sumber