Команда Bash awk с кавычками

8

Я пытался найти ответ на этот вопрос некоторое время. Я пишу быстрый скрипт для запуска команды, основанной на выводе из 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

Проблема в том, что -cаргумент принимает команду в одинарных кавычках, и я не могу понять, как правильно избежать этого, а также того, что $1не распространяется на имя пользователя.

По сути, я просто пытаюсь получить его на выходе:

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

так далее...

ZCT
источник
2
Возможный дубликат Как избежать кавычек в оболочке?
Иордания
2
Ваш цикл повторяется только один раз.
Иордания
@jordanm Я не думаю, что это относится кawk
jesse_b
2
@Jesse_b его проблема цитирования заключается в цитировании оболочки, на самом деле не имеет ничего общего с awk. Ему нужно избегать вложенных одинарных кавычек.
Иордания

Ответы:

13

Чтобы запустить команду xfs_quota -x -c 'limit bsoft=5g bhard=6g USER' /homeдля каждого пользователя, USERчей UID хотя бы $ID_minimumминимален, рассмотрите сначала анализ этих пользователей, а затем фактически запустите команду, а не пытайтесь создать строку, представляющую команду, которую вы хотите запустить.

Если вы создадите командную строку, вам придется к evalней. Это легко и легко ошибиться. Лучше просто получить список имен пользователей и затем запустить команду.

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

Обратите внимание, что нет реальной необходимости в одинарных кавычках вокруг аргумента после -c. Здесь я использую двойные кавычки, потому что я хочу, чтобы оболочка раскрыла $userпеременную, которая содержит значения, извлеченные с помощью awk.

Я использую ${ID_minimum:-1000}при присвоении значения minпеременной в awkкоманде. Это расширится до значения $ID_minimumили, 1000если эта переменная пуста или не установлена.


Если вы действительно хотите, вы можете заставить вышеприведенный цикл выводить команды вместо их выполнения:

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

Еще раз обратите внимание, что использование двойных кавычек в выводимой командной строке (вместо одинарных кавычек) никоим образом не будет путать оболочку, если вы будете выполнять сгенерированные команды с использованием evalили каким-либо другим способом. Если это вас беспокоит, просто поменяйте местами одинарные и двойные кавычки в первом аргументе printfвыше.

Кусалананда
источник
1
единственный ответ, чтобы правильно использовать getent вместо разбора / etc / passwd.
Cas
1
@cas macOS - это Unix без getent(на рабочем столе, во всяком случае), и вопрос не помечен как «linux».
TheDudeAbides
2
@TheDudeAbides Предполагая, что пользователь выбрал UID 1000, потому что это разрыв между учетными записями системных служб и учетными записями пользователей в их системе, мы можем заключить, что этот пользователь не работает в macOS (первая учетная запись пользователя 501). Кроме того, использование утилит XFS в macOS довольно редко встречается, поскольку Apple не поддерживает XFS. Кроме того, на /homeсамом деле не используется в macOS (он есть, но обычно пустой).
Кусалананда
@ Кусалананда Точка занята. Я был слеп к части XFS.
TheDudeAbides
1
@TheDudeAbides getent не только для Linux. это также на netbsd, freebsd и solaris по крайней мере.
Cas
6
for f in /etc/passwd;

Это немного глупо, поскольку в действительности нет цикла с одним значением.

Но проблема, кажется, печатает одинарные кавычки из awk. Вы можете избежать их в оболочке, но вы также можете использовать обратную косую черту в awk для их печати. символ с числовым значением ОООвосьмеричном выражении ), то есть . Так что это будет один из способов сделать это:\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

Вы можете использовать аналогичный escape в шестнадцатеричном \x27формате, но в некоторых реализациях он может быть неверно истолкован, если следующий символ является действительной шестнадцатеричной цифрой. (И, конечно, я предполагал ASCII или ASCII-совместимый набор символов, например UTF-8.)

ilkkachu
источник
Обратите внимание, что здесь все в порядке, поскольку lэто не шестнадцатеричная цифра, а "\x27df\n"будет рассматриваться как "\x27" "df"в awbox или mawk busybox, а как "\xdf"( 0x27dfприведение к 8-битному символу) в оригинальном awk и gawk (именно поэтому POSIX не указывает \xHH). Octals ( \047) не та же проблема и POSIX. В любом случае это предполагает систему ASCII (разумное предположение в наши дни).
Стефан
6

Используйте -f -опцию awk, чтобы взять скрипт из stdin и документ здесь:

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
источник
2

Это сделал это.

awk -F: -vID=$ID_minimum '$3>=1000 && $1!="nfsnobody" { print "xfs_quota -x -c '"'"'limit bsoft=5g bhard=6g ''"$1"'''"'"' /home "}' /etc/passwd
ZCT
источник
1
@Jesse_b, да, вы не можете помещать одинарные кавычки в строку, заключенную в одинарные кавычки, поэтому сначала нужно завершить ее, а затем заключить в кавычки, которые вы хотите вставить. Неважно, если вы делаете '\''или '"'"'. Оба работают, оба выглядят надоедливыми.
ilkkachu
@OP, надеюсь, вы видели комментарий Джорданма о том, что ваш цикл не нужен. Ваш цикл запускается только один раз, поэтому он не отличается от простого запуска одной и той же команды awk вне цикла.
jesse_b
1

Это ужасная уловка, но это быстро и легко ...

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
источник
1

Это выглядит как идеальная возможность нанять вас xargs(или 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

Преимущество использования xargsили parallelзаключается в том, что вы можете просто удалить,echo когда будете готовы запустить команду по-настоящему (возможно, заменив ее sudo, если необходимо).

Вы также можете использовать опции -p/ --interactive(последние только для GNU) или --dry-run( parallelтолько) этих утилит , чтобы получить подтверждение перед запуском каждого или просто посмотреть, что будет запущено, прежде чем запускать его.

Общий метод, использованный выше, должен работать в большинстве Unix-систем и не требует специфических для GNU xargsопций. Двойные кавычки делают необходимость быть «убежали» , так что они появляются буквально на выходе. Обратите внимание , что «строка замены» {}, в xargs -I{}может быть все , что угодно, и -Iпредполагает -L1(запустить одну команду за входной линии , а не дозированием их).

GNU Parallel не требует -Iопции ( {}это замена строки по умолчанию), и дает вам мгновенный бонус работает много рабочих мест , параллельно, даже если вы не хотите заморачиваться узнать о какой - либо из его других особенностей .

В качестве примечания, я даже не уверен , что xfs_quota«S -cопция должна быть использована , как это, хотя у меня нет XFS не файловые системы удобно проверить. Вам, возможно, даже не понадобилось иметь дело с строкой в ​​кавычках (если вы не ожидаете, что имена пользователей с пробелами в них, что я думаю, возможно), так как похоже, что вы можете задать несколько -cпараметров в одной командной строке, в зависимости от на страницу xfsprogsруководства, включенную с 4.5.

Чувак пребывает
источник
0

С GNU Parallel вы можете делать:

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

Объяснение:

--colsep :Разделить на:
-qне разбивать команду на пробелы (сохраняйте '...' как одну строку).
{=1 ... =}Оцените это выражение perl по первому аргументу строки.
$_ eq "nfsnobody" and skip();Если значение == nfsnobody: пропустить,
$arg[3] <= 1000 and skip();если аргумент3 <= 1000: пропустить.

Оле Танге
источник