Мне нужен сценарий оболочки bash (для моей системы Debian 12), который принимает несколько путей в качестве аргументов, а затем проверяет, можно ли успешно создать файл в каждом из них. Затем результат необходимо поместить в формат json.
Команда для запуска должна быть следующей:
./checkavailable.sh /the/first/path /the/second/path
(где может быть любое количество входных путей, но хотя бы один) и я хотел бы, чтобы результат был в следующем формате:
[
{ "is_available": 0, "path": "/the/first/path/" },
{ "is_available": 1, "path": "/the/second/path/" }
]
В настоящее время мне удалось добиться этого с помощью следующего скрипта:
#!/bin/bash
# Check if first path is available
if touch $1/checkalivefile ; then
avail_path1=1
rm $1/checkalivefile
else
avail_path1=0
fi
# Check if second path is available
if touch $2/checkalivefile ; then
avail_path2=1
$2/checkalivefile
else
avail_path2=0
fi
echo "["
printf " { \"is_available\": "$avail_path1", \"path\": \"$1\" } "
printf ",\n"
printf " { \"is_available\": "$avail_path2", \"path\": \"$2\" } "
echo
echo "]"
но он, очевидно, немного примитивен и жестко запрограммирован для работы только с аргументами x2 и не масштабируется до дополнительных аргументов.
Я мог бы попытаться добавить цикл сейчас. Однако я наткнулся на этот скрипт, который, похоже, делает что-то похожее (в данном случае для получения информации об использовании диска), но использует awk вместо цикла:
#!/bin/bash
echo "["
du -s -B1 "$@" | awk '{if (NR!=1) {printf ",\n"};printf " { \"dir_size_bytes\": "$1", \"path\": \""$2"\" }";}'
echo
echo "]"
Может ли кто-нибудь показать, как этот подход с использованием «awk» можно использовать в моем случае? Я новичок в bash-скриптах, поэтому надеюсь чему-нибудь научиться.
----РЕДАКТИРОВАТЬ----
В контексте, причина генерации вывода json в этом конкретном формате заключается в том, что его можно интерпретировать непосредственно плагинами ввода в Telegraf, которые могут автоматически конвертировать его в поля и метки метрик для отправки в базу данных.
@jhnc Если вы имеете в виду, когда входные аргументы просто дублируются? Тогда можно дублировать вывод (хотя я не ожидаю, что это когда-либо произойдет).
@jhnc [ { "is_available": 1, "path": "/dir/file1" }, { "is_available": 1, "path": "/dir/file1" } ]
Я ожидаю, что хотя бы один будет недоступен. Как только путь был «использован» потребителем в первый раз, он больше не будет доступен в следующий раз. Возможно, было бы лучше дедуплицировать входные пути, чтобы в таких случаях выводилась только одна запись.
@teeeeee Представлено ваше последнее изменение rm $1/checkalivefile
. Тогда зачем вообще touch
файл? Вы можете просто test
указать его наличие с помощью -e
, возможность записи его родителя с помощью -w
и т. д.
@pmf Намерение состояло в том, чтобы быть абсолютно уверенным, что я могу создать новый файл в определенных каталогах (чтобы проверить, что определенные диски все еще подключены и т. д.). Если я постоянно храню «checkalivefile» в каждом каталоге, то иногда замечаю, что он все еще может оставаться доступным (например, с помощью ls) даже после внезапного отключения диска. Итак, я хотел убедиться, создав вместо этого новый файл (а затем удалив его в случае успеха).
@teeeeee Но touch
не будет жаловаться, если файл уже есть...
@pmf Да, все может быть хорошо. Опять же, это было просто для моего спокойствия, что до проверки в папке ничего не было, тогда можно было создать совершенно новый файл.
Основная концепция программирования: если вам нужно применить операцию к коллекции элементов, вы перебираете их. В большинстве языков программирования есть два основных инструмента для итерации: цикл и рекурсивные функции. В bash вы обнаружите, что использование рекурсивной функции не очень удобно (попробуйте, если хотите попрактиковаться!), поэтому цикл — очевидный подход к вашей проблеме. Полезная команда, встроенная в bash для циклов, — это for
, но в других ситуациях вы, возможно, найдете внешние команды xargs
или find
удобными для работы с циклами.
Кстати, подход, который вы пробовали, использует цикл; вы просто используете awk
для зацикливания. Однако я счел бы использование awk
в вашем случае довольно неестественным и, возможно, привело бы к неуклюжему коду. Например, ваша awk-программа предполагает, что имена путей не содержат пробельных символов, что является несколько нереалистичным предположением.
Это перекрестная публикация с unix.stackexchange.com/questions/782873/…
@pmf Я понимаю, что удаление их с помощью rm является обременительным и ненужным, если «touch не будет жаловаться, если файл уже существует». Но в чем тогда преимущество использования «теста» перед «прикосновением»?
@teeeeee Сообщите нам, при каких обстоятельствах (файл существует, доступен для записи, родители существуют, доступны для записи и т. д.) вы хотите is_available
переносить 0
или 1
(к вашему сведению: в JSON также есть логические значения). Если это исчерпывающе определено, мы можем сказать вам, что test
может сделать для вас. В настоящее время touch
не удастся, если он не сможет обновить время доступа и изменения этого пути, независимо от того, присутствует файл или нет. В зависимости от ваших реальных целей также рассмотрите возможность использования touch -c
.
@pmf У меня есть общие ресурсы NFS, смонтированные в разных каталогах. Мне нужен способ проверить, потеряно ли соединение. Поэтому я хочу иметь возможность проверять, что новые файлы могут быть успешно записаны в эти каталоги, где должны появиться монтируемые файлы. Поэтому я считаю, что меня не волнуют родительские каталоги или наличие каких-либо файлов (ранее я создавал фиктивный файл только для проверки того, что каталог присутствует и доступен для записи). Имеет ли это смысл?
@pmf Я не уверен, что понимаю, как/когда/touch потерпит неудачу. Извините, а можно поподробнее?
@jhnc Я понимаю, что ты имеешь в виду, ты прав. В этом смысле мой вопрос, возможно, тогда немного ввел в заблуждение. Я не пытаюсь проверить, можно ли создать файл с определенным именем (вы правы, если он уже существует, то технически он не будет «доступен». ). На самом деле я пытаюсь проверить, доступен ли путь для записи в него новых файлов.
@pmf Я пытаюсь использовать тест вместо касания, но поведение выглядит неправильным. После отключения диска «test -w» по-прежнему возвращает «истину» для каталога: myuser@mymachine:~$ echo $(test -w /path/to/dir) $? 1
Однако... «прикосновение» не удается, как и должно быть: myuser@mymachine:~$ touch /path/to/dir/checkalive.txt touch: cannot touch '/path/to/dir/checkalive.txt': Input/output error
Может ли это быть потому, что каталог после этого зависает? Это одна из причин, по которой я в первую очередь был склонен использовать touch/rm.
Пожалуйста, не публикуйте мультипосты — unix.stackexchange.com/questions/782873/….
@teeeeee Re «У меня смонтированы общие ресурсы NFS […] Раньше я создавал фиктивный файл только для проверки того, что каталог присутствует и доступен для записи»: вы можете просто проверить, используя [ -d "$path" ]
(успешно, если путь (где вы хотели создать фиктивный файл) существует и является каталогом) и [ -w "$path" ]
(удаляется, если этот путь (каталог по первой проверке) доступен для записи). Но это не скажет вам, смонтировано ли там что-то (точка монтирования без монтирования, вероятно, все еще является каталогом, доступным для записи). Лучше используйте команды mount
, df
или (особенно для монтирования NFS) showmount
, чтобы проверить это.
Обратите внимание, что touch
может принимать несколько аргументов, поэтому touch "$@"
уже выполнит эту команду по всем путям, указанным в вашем скрипте. В зависимости от того, что на самом деле означает is_available
в желаемом выводе, вы можете просто запросить, действительно ли файлы присутствуют, доступны для записи и т. д.
Вот подход, который по-прежнему перебирает аргументы вашего скрипта с помощью цикла for
, применяет touch
индивидуально и проверяет его (отрицаемый) код завершения $?
. Затем они передаются в простой скрипт jq, который возвращает действительную кодировку JSON, независимо от каких-либо странных символов в предоставленных путях.
#!/bin/sh
for path; do ! touch "$path/dummy"; echo $?; rm -f "$path/dummy"; done |
jq -n --args '[$ARGS.positional[] | {is_available: input, path: .}]' "$@"
Примечание. Проглотите stderr, используя touch … 2>/dev/null
, чтобы отключить сообщения об ошибках, выводимые в случае сбоя.
У меня есть общие ресурсы NFS, смонтированные в разных каталогах. Мне нужен способ проверить, потеряно ли соединение. Поэтому я хочу иметь возможность проверять, что новые файлы могут быть успешно записаны в эти каталоги, где должны появиться монтируемые файлы. […] Раньше я создавал фиктивный файл только для проверки того, что каталог существует и доступен для записи.
Боюсь, touch
вам в этом случае не поможет (как и test
с -d
и -w
), так как они не скажут вам, смонтировано ли там что-то или нет. В конце концов, несмонтированная точка монтирования вполне может быть каталогом, доступным для записи. Таким образом, лучше используйте команду df
, в которой перечислены фактические точки монтирования, сужая их по файловой системе, например. используя -t nfs4
, и продолжайте, как раньше.
#!/bin/sh
for path; do ! df -t nfs4 "$path" >/dev/null 2>/dev/null; echo $?; done |
jq -n --args '[$ARGS.positional[] | {is_available: input, path: .}]' "$@"
Спасибо, но я получаю сообщение об ошибке «jq: команда не найдена». [… Я] не хочу устанавливать новый пакет
Нет необходимости устанавливать какие-либо новые пакеты: вы можете просто скачать и запустить двоичный файл напрямую. Кроме того, имейте в виду, что при использовании произвольных входных строк (в данном случае это имена путей, которые могут содержать практически любой символ) вам будет сложно правильно закодировать их для вывода вручную созданного JSON. Поэтому я настоятельно не рекомендую никому использовать следующую альтернативу в производстве, но для подхода, при котором строки просто заключаются в кавычки, несмотря ни на что, рискуя получить недопустимый вывод JSON, замените (в любом из приведенных выше сценариев) строку jq на:
while read -r is_av; do
printf '{"is_available": %d, "path": "%s"}\n,' "$is_av" "$1"; shift
done | sed '1s/^/[/;$s/,/]/'
Спасибо, но я получаю сообщение об ошибке «jq: команда не найдена». Можете ли вы привести пример, подобный приведенному в ОП (в котором используется awk), чтобы он работал на минимальном Debian без установки дополнительных пакетов?
jq — это стандартный инструмент (см. ссылку выше), который должен быть доступен в ваших репозиториях Debian. Хотя я настоятельно не рекомендую вам пытаться вручную кодировать произвольные входные строки в формате JSON, вот один (хрупкий!) способ использования awk: for path; do ! touch "$path/checkalivefile"; printf '{"is_available": "%d", "path": "%s"}\n' $? "$path"; done | awk 'BEGIN {print "["} NR>1 {print ","} {print} END {print "]"}'
. Не используйте это в производстве.
Спасибо. Я запускаю скрипт внутри предварительно скомпилированного докер-контейнера, поэтому не хочу устанавливать новый пакет. В вопросе я также удаляю файл после успешного создания (с помощью rm). Можете ли вы изменить версию awk, чтобы включить это?
Что касается «не хочу устанавливать новый пакет»: устанавливать не нужно, вы можете просто загрузить и запустить двоичный файл. По поводу «Я также удаляю файл»: см. мой комментарий выше и/или просто вставьте rm "$path/checkalivefile"
после echo $?
(используйте флаг -f
, чтобы отключить ошибку «файл не найден»).
Я знаю, что lq
— это мощный инструмент JSON, но требовать для этого jq
кажется излишним, поскольку у нас есть всего лишь 1 (довольно простая) отформатированная строка, которую нужно применить.
@F.Hauri А наоборот! Как только вам придется иметь дело с произвольными входными данными (в данном случае это имена путей, которые могут содержать практически любые символы), вам лучше использовать надежный кодировщик JSON, чем заново изобретать механизм, пытаясь вручную подготовиться к каждой возможности. Тем не менее, это не обязательно должен быть jq, вероятно, подойдет любой проверенный процессор JSON.
Совершенно странно хотеть выводить данные в формате JSON, но не устанавливать стандартный инструмент де-факто для обработки JSON.
У нас есть две операции... 1) запустить образец touch
и 2) хорошенько распечатать вывод. Запуск touch
изнутри awk
— это ненужные накладные расходы, а вызов сценария awk
, чтобы просто красиво напечатать то, что мы можем легко сделать в bash
, — это излишество (по моему мнению). Итак, я бы предпочел сделать все это в bash
.
Общий подход:
touch
файл, то:rm
файл'['
is_available
(предисловие с окончанием предыдущей строки)']'
ending for previous line
заботится о печати окончания ,
для всех строк, кроме последней.available[]
для последующей bash
обработки.Одна bash
идея:
$ cat gen.jq
#!/bin/bash
unset available
available=()
count=0
pfx = "" # 1st 'is_available' line prefaced with nothing
for path in "$@"
do
tgt = "${path}/.checkalivefile"
if touch "${tgt}" 2>/dev/null
then
rm -f "${tgt}"
available+=( "${path}" )
(( count == 0 )) && echo "[" # if this is the 1st time printing an 'is_available'
# line then print the opening '['
printf "%b { \"is_available\": %s, \"path\": \"%s\" }" "${pfx}" "${count}" "${path}"
(( count++ ))
pfx = ",\n" # 2nd-nth 'is_available' lines prefaced with ','
# to close out previous line + linefeed
fi
done
(( count > 0 )) && printf "\n]\n" # if we printed at least one 'is_available' line
# so close out the last line (\n) and print the
# closing ']'
# typeset -p available # uncomment to display array contents
Настраивать:
$ cd /tmp
$ mkdir -p dir1 dir1/subdir1 dir1/subdir2
$ chmod 000 dir1/subdir2
$ tree /tmp
/tmp
├── dir1
│ ├── subdir1
│ └── subdir2 [error opening dir]
Запуск скрипта:
$ ./gen.jq /tmp/dir1 /tmp/dir1/subdir1 /tmp/dir1/subdir2 /tmp/dir2
[
{ "is_available": 0, "path": "/tmp/dir1" },
{ "is_available": 1, "path": "/tmp/dir1/subdir1" }
]
Если строка typeset -p available
раскомментирована, также генерируется следующее:
declare -a available=([0] = "/tmp/dir1" [1] = "/tmp/dir1/subdir1")
Использование touch
и rm
для записи и последующего удаления файла обременительно. Оболочка уже предоставляет такую возможность.
for path; do
test -w "$path" && echo "$path"
done
Требование вывода JSON является странным. Инструменты Unix, вероятно, должны создавать текст по умолчанию, хотя всегда приятно иметь возможность создавать JSON.
printf '%s' "["
sep = ""
for path; do
test -w "$path"
printf '%s{"is_available": %d, "path": "%s"}' "$sep" $((1 - $?)) "${path//[\\\"]/\\&}"
sep = ","
done
printf '%s' "]"
Выражение ${path//[\\\"]/\\&}
требует Bash. Это расширение параметра будет экранировать любую буквальную обратную косую черту или двойную кавычку.
Если вы специально хотите проверить, можно ли создать файл с именем checkalivefile
по каждому из передаваемых вами путей, нетрудно изменить для этого сценарий; но для меня имеет смысл передать то, что вы действительно хотите протестировать (и поэтому вы должны передать /the/first/path/checkalivefile
и /the/second/path/checkalivefile
, если это то, что вы хотите проверить).
Это не приведет к тому, что вместо каталогов будут указаны файлы ответов! Другими словами: будет соответствовать и записываемым файлам.
Неясно, передается ли ОП в каталогах или файлах. Судя по ожидаемому результату, эти пути на самом деле являются каталогами; но в соответствии с принципами Unix это просто возвращает входные данные в качестве выходных. Если вы хотите знать, доступен ли /
для записи, вы можете это проверить. Если вы хотите знать, можете ли вы создавать /foo
, вы можете это проверить.
В заголовке вопроса говорится, что проверяется, можно ли успешно создать там файлы. Тогда это не должны быть файлы, фифосы или устройства!
Я правда не уверен. Можно ли создавать файлы в /
? Если у вас есть разрешения, вы можете создать /foo
.
Я действительно уверен, что файл не может быть создан в файлах! Вам нужно использовать test -d
и test -w
! (Что-то вроде test -d "$path" && test -w "$path"; echo $((1-$?))
;-)
А чтобы избежать проблем, когда $?
становится выше 1
, вы можете использовать $(( $? ? 0 : 1 ))
Если /foo
существует, но у вас есть разрешения на перезапись, скрипт сообщит вам об этом.
Давайте продолжим обсуждение в чате.
Привет обоим, у меня есть общие ресурсы NFS, смонтированные в различных каталогах системы, и мне нужен способ проверить, не потерялось ли соединение. Поэтому я хочу иметь возможность проверять, что новые файлы могут быть успешно записаны в эти каталоги, где должны появиться монтируемые файлы. Мне не нужно когда-либо проверять файлы напрямую (раньше я создавал только фиктивный файл для проверки того, что каталог присутствует и доступен для записи). Входные данные должны быть только путями к каталогам, и меня не волнует защита от передачи файлов в качестве входных данных.
Существует ли при использовании test -w path
какая-то потенциальная проблема, поскольку возврат 1 является ложным, а возврат 0 — истинным в bash?
Арифметическая оценка $((1-$?))
решает эту проблему. Если $?
равен 0, результат равен 1, и наоборот.
@tripleee, я вижу. хороший. У меня есть символ двоеточия (:) между «путем» и именем пути. У тебя есть запятая. (Хотя между полями «isavailable» и «path» используется запятая. Можете ли вы это исправить? Кроме того, можете ли вы добавить новую строку между каждым каталогом для удобства чтения печати, как в OP?
@tripleee Кроме того, я хочу использовать подход touch/rm
, потому что использование test -w
не дает точного результата, когда каталог является монтированием NFS. Тем не менее, касание по-прежнему показывает, пригоден ли к использованию подключенный диск (он выйдет из строя либо из-за ошибки ввода-вывода, либо зависнет. Последнее можно обнаружить с помощью простой оболочки тайм-аута). Можете ли вы обновить свой ответ, чтобы отразить это, потому что ваш ответ - это почти то, что я ищу с расширением параметров.
какой должен быть результат
./checkavailable.sh /dir/file1 /dir/file1
?