Я использую jq ...
Я понимаю, что jq
популярен здесь, но я собираюсь рекомендовать Xidel для этой задачи.
Хотя в первую очередь это анализатор HTML / XML / JSON (с использованием шаблонов CSS, XPath, XQuery, JSONiq и шаблонов), Xidel также может обрабатывать основной текст.
Вы говорите, что хотите иметь jsonкак этот:
{
"hostname": "example",
"users": [
{
"username": "example1",
"details": {
"LastPasswordChange": "Nov 12, 2018",
"PasswordExpires": "never",
"PasswordInactive": "never",
"AccountExpires": "never",
"MinDaysBetweenPasswordChange": "0",
"MaxDaysBetweenPasswordChange": "99999",
"NumOfDaysOfWarningBeforePasswordExpires": "7"
}
},
{
"username": "example2",
"details": {
"LastPasswordChange": "Nov 13, 2018",
"PasswordExpires": "never",
"PasswordInactive": "never",
"AccountExpires": "never",
"MinDaysBetweenPasswordChange": "0",
"MaxDaysBetweenPasswordChange": "99999",
"NumOfDaysOfWarningBeforePasswordExpires": "7"
}
}
]
}
Это означает, что массив users
должен быть заполнен объектами (созданными из вывода chage
программы) для каждого пользователя отдельно .Вот почему я бы выбрал другой подход.
Допустим, getent passwd | egrep -v '/s?bin/(nologin|shutdown|sync|halt)' | cut -d: -f1
возвращает:
root
user1
user2
Это мы собираемся направить в Xidel, чтобы сначала создать базовый json:
getent passwd | egrep -v '/s?bin/(nologin|shutdown|sync|halt)' | cut -d: -f1 | xidel -s - --xquery '
{
"hostname":"'$(hostname)'",
"users":x:lines($raw) ! {
"username":.
}
}
'
- Нет необходимости в
hostname &&
перед вашей исходной командой, потому что вы просто вызываете ее из Xidel (что также делает запрос намного проще). - С
x:lines($raw)
вы создаете последовательность каждой новой строки из ввода Xidel (в данном случае stdin).В качестве альтернативы можно использовать tokenize($raw,"\n")
.
Вывод запроса:
{
"hostname": "user",
"users": [
{
"username": "root"
},
{
"username": "user1"
},
{
"username": "user2"
}
]
}
Далее мы будем вызывать chage
из Xidel для создания details
объект:
getent passwd | egrep -v '/s?bin/(nologin|shutdown|sync|halt)' | cut -d: -f1 | xidel -s - --xquery '
{
"hostname":"'$(hostname)'",
"users":x:lines($raw) ! {
"username":.,
"details":{|
for $x in x:lines(
system(x"chage -l {.}")
)
let $a:=tokenize(
$x,
":"
)
return {
normalize-space($a[1]):normalize-space($a[2])
}
|}
}
}
'
system(x"chage -l {.}")
возвращает выходные данные chage -l root
, chage -l user1
и chage -l user2
. - С
x:lines()
вокруг него вы снова создаетепоследовательность каждой новой строки.Первая строка для вывода user2
, например:
Last password change : Oct 23, 2018
- Для каждого элемента в этой последовательности (или строки) создается переменная
$a
, которая содержит последовательностьэтого элемента, разделенного на две части, с :
в качестве разделителя.$a
Таким образом, для этого первого элемента:
Last password change # white-space up untill this point!
Oct 23, 2018
- Наконец создается пара атрибут-значение с нормализованным пробелом:
{
"Last password change": "Oct 23, 2018"
}
Вывод запроса:
{
"hostname": "user",
"users": [
{
"username": "root",
"details": {
"Last password change": "never",
"Password expires": "never",
"Password inactive": "never",
"Account expires": "never",
"Minimum number of days between password change": "0",
"Maximum number of days between password change": "99999",
"Number of days of warning before password expires": "7"
}
},
{
"username": "user1",
"details": {
"Last password change": "never",
"Password expires": "never",
"Password inactive": "never",
"Account expires": "never",
"Minimum number of days between password change": "0",
"Maximum number of days between password change": "99999",
"Number of days of warning before password expires": "7"
}
},
{
"username": "user2",
"details": {
"Last password change": "Oct 23, 2018",
"Password expires": "never",
"Password inactive": "never",
"Account expires": "never",
"Minimum number of days between password change": "0",
"Maximum number of days between password change": "99999",
"Number of days of warning before password expires": "7"
}
}
]
}
Вы можете пойти еще дальше:
getent passwd | egrep -v '/s?bin/(nologin|shutdown|sync|halt)' | cut -d: -f1 | xidel -s - --xquery '
{
"hostname":"'$(hostname)'",
"users":x:lines($raw) ! {
"username":.,
"details":{|
for $x in x:lines(
system(x"chage -l {.}")
)
let $a:=tokenize(
$x,
":"
)
return {
string-join(
for $x in tokenize(
normalize-space($a[1]),
" "
)
return
upper-case(
substring(
$x,
1,
1
)
)||substring(
$x,
2
)
):normalize-space($a[2]) ! (
if (. castable as decimal) then
number(.)
else
.
)
}
|}
}
}
'
- Вместо
Last password change
, с ...
string-join(for $x in tokenize(normalize-space($a[1])," ") return upper-case(substring($x,1,1))||substring($x,2))
... возвращается: LastPasswordChange
.
- Вместо
"99999"
(строка), с ...
normalize-space($a[2]) ! (if (. castable as decimal) then number(.) else .)
... он возвращает: 99999
как xs: десятичный атомарный тип.
Вывод запроса:
{
"hostname": "user",
"users": [
{
"username": "root",
"details": {
"LastPasswordChange": "never",
"PasswordExpires": "never",
"PasswordInactive": "never",
"AccountExpires": "never",
"MinimumNumberOfDaysBetweenPasswordChange": 0,
"MaximumNumberOfDaysBetweenPasswordChange": 99999,
"NumberOfDaysOfWarningBeforePasswordExpires": 7
}
},
{
"username": "user1",
"details": {
"LastPasswordChange": "never",
"PasswordExpires": "never",
"PasswordInactive": "never",
"AccountExpires": "never",
"MinimumNumberOfDaysBetweenPasswordChange": 0,
"MaximumNumberOfDaysBetweenPasswordChange": 99999,
"NumberOfDaysOfWarningBeforePasswordExpires": 7
}
},
{
"username": "user2",
"details": {
"LastPasswordChange": "Oct 23, 2018",
"PasswordExpires": "never",
"PasswordInactive": "never",
"AccountExpires": "never",
"MinimumNumberOfDaysBetweenPasswordChange": 0,
"MaximumNumberOfDaysBetweenPasswordChange": 99999,
"NumberOfDaysOfWarningBeforePasswordExpires": 7
}
}
]
}
"Предварительно подтвержденный" запрос гораздо легче читать, но если вы предпочитаете«минимизированная» версия:
getent passwd | egrep -v '/s?bin/(nologin|shutdown|sync|halt)' | cut -d: -f1 | xidel -s - --xquery '{"hostname":"'$(hostname)'","users":x:lines($raw) ! {"username":.,"details":{|for $x in x:lines(system(x"chage -l {.}")) let $a:=tokenize($x,":") return {string-join(for $x in tokenize(normalize-space($a[1])," ") return upper-case(substring($x,1,1))||substring($x,2)):normalize-space($a[2]) ! (if (. castable as decimal) then number(.) else .)}|}}}'