Скрипт Bash, который принимает несколько аргументов пути и проверяет, можно ли там успешно создать файлы

Мне нужен сценарий оболочки 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, которые могут автоматически конвертировать его в поля и метки метрик для отправки в базу данных.

какой должен быть результат ./checkavailable.sh /dir/file1 /dir/file1 ?

jhnc 02.09.2024 01:55

@jhnc Если вы имеете в виду, когда входные аргументы просто дублируются? Тогда можно дублировать вывод (хотя я не ожидаю, что это когда-либо произойдет).

teeeeee 02.09.2024 01:59

@jhnc [ { "is_available": 1, "path": "/dir/file1" }, { "is_available": 1, "path": "/dir/file1" } ]

teeeeee 02.09.2024 01:59

Я ожидаю, что хотя бы один будет недоступен. Как только путь был «использован» потребителем в первый раз, он больше не будет доступен в следующий раз. Возможно, было бы лучше дедуплицировать входные пути, чтобы в таких случаях выводилась только одна запись.

jhnc 02.09.2024 02:03

@teeeeee Представлено ваше последнее изменение rm $1/checkalivefile. Тогда зачем вообще touch файл? Вы можете просто test указать его наличие с помощью -e, возможность записи его родителя с помощью -w и т. д.

pmf 02.09.2024 02:04

@pmf Намерение состояло в том, чтобы быть абсолютно уверенным, что я могу создать новый файл в определенных каталогах (чтобы проверить, что определенные диски все еще подключены и т. д.). Если я постоянно храню «checkalivefile» в каждом каталоге, то иногда замечаю, что он все еще может оставаться доступным (например, с помощью ls) даже после внезапного отключения диска. Итак, я хотел убедиться, создав вместо этого новый файл (а затем удалив его в случае успеха).

teeeeee 02.09.2024 02:10

@teeeeee Но touch не будет жаловаться, если файл уже есть...

pmf 02.09.2024 02:11

@pmf Да, все может быть хорошо. Опять же, это было просто для моего спокойствия, что до проверки в папке ничего не было, тогда можно было создать совершенно новый файл.

teeeeee 02.09.2024 02:15

Основная концепция программирования: если вам нужно применить операцию к коллекции элементов, вы перебираете их. В большинстве языков программирования есть два основных инструмента для итерации: цикл и рекурсивные функции. В bash вы обнаружите, что использование рекурсивной функции не очень удобно (попробуйте, если хотите попрактиковаться!), поэтому цикл — очевидный подход к вашей проблеме. Полезная команда, встроенная в bash для циклов, — это for, но в других ситуациях вы, возможно, найдете внешние команды xargs или find удобными для работы с циклами.

user1934428 02.09.2024 08:23

Кстати, подход, который вы пробовали, использует цикл; вы просто используете awk для зацикливания. Однако я счел бы использование awk в вашем случае довольно неестественным и, возможно, привело бы к неуклюжему коду. Например, ваша awk-программа предполагает, что имена путей не содержат пробельных символов, что является несколько нереалистичным предположением.

user1934428 02.09.2024 08:29

Это перекрестная публикация с unix.stackexchange.com/questions/782873/…

Jetchisel 02.09.2024 11:47

@pmf Я понимаю, что удаление их с помощью rm является обременительным и ненужным, если «touch не будет жаловаться, если файл уже существует». Но в чем тогда преимущество использования «теста» перед «прикосновением»?

teeeeee 02.09.2024 12:34

@teeeeee Сообщите нам, при каких обстоятельствах (файл существует, доступен для записи, родители существуют, доступны для записи и т. д.) вы хотите is_available переносить 0 или 1 (к вашему сведению: в JSON также есть логические значения). Если это исчерпывающе определено, мы можем сказать вам, что test может сделать для вас. В настоящее время touch не удастся, если он не сможет обновить время доступа и изменения этого пути, независимо от того, присутствует файл или нет. В зависимости от ваших реальных целей также рассмотрите возможность использования touch -c.

pmf 02.09.2024 12:46

@pmf У меня есть общие ресурсы NFS, смонтированные в разных каталогах. Мне нужен способ проверить, потеряно ли соединение. Поэтому я хочу иметь возможность проверять, что новые файлы могут быть успешно записаны в эти каталоги, где должны появиться монтируемые файлы. Поэтому я считаю, что меня не волнуют родительские каталоги или наличие каких-либо файлов (ранее я создавал фиктивный файл только для проверки того, что каталог присутствует и доступен для записи). Имеет ли это смысл?

teeeeee 02.09.2024 13:01

@pmf Я не уверен, что понимаю, как/когда/touch потерпит неудачу. Извините, а можно поподробнее?

teeeeee 02.09.2024 13:02

@jhnc Я понимаю, что ты имеешь в виду, ты прав. В этом смысле мой вопрос, возможно, тогда немного ввел в заблуждение. Я не пытаюсь проверить, можно ли создать файл с определенным именем (вы правы, если он уже существует, то технически он не будет «доступен». ). На самом деле я пытаюсь проверить, доступен ли путь для записи в него новых файлов.

teeeeee 02.09.2024 13:12

@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.

teeeeee 02.09.2024 14:45

Пожалуйста, не публикуйте мультипосты — unix.stackexchange.com/questions/782873/….

Ed Morton 02.09.2024 15:35

@teeeeee Re «У меня смонтированы общие ресурсы NFS […] Раньше я создавал фиктивный файл только для проверки того, что каталог присутствует и доступен для записи»: вы можете просто проверить, используя [ -d "$path" ] (успешно, если путь (где вы хотели создать фиктивный файл) существует и является каталогом) и [ -w "$path" ] (удаляется, если этот путь (каталог по первой проверке) доступен для записи). Но это не скажет вам, смонтировано ли там что-то (точка монтирования без монтирования, вероятно, все еще является каталогом, доступным для записи). Лучше используйте команды mount, df или (особенно для монтирования NFS) showmount, чтобы проверить это.

pmf 02.09.2024 22:02
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
19
563
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Обратите внимание, что 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 без установки дополнительных пакетов?

teeeeee 02.09.2024 01:33

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 "]"}'. Не используйте это в производстве.

pmf 02.09.2024 01:55

Спасибо. Я запускаю скрипт внутри предварительно скомпилированного докер-контейнера, поэтому не хочу устанавливать новый пакет. В вопросе я также удаляю файл после успешного создания (с помощью rm). Можете ли вы изменить версию awk, чтобы включить это?

teeeeee 02.09.2024 02:02

Что касается «не хочу устанавливать новый пакет»: устанавливать не нужно, вы можете просто загрузить и запустить двоичный файл. По поводу «Я также удаляю файл»: см. мой комментарий выше и/или просто вставьте rm "$path/checkalivefile" после echo $? (используйте флаг -f, чтобы отключить ошибку «файл не найден»).

pmf 02.09.2024 02:10

Я знаю, что lq — это мощный инструмент JSON, но требовать для этого jq кажется излишним, поскольку у нас есть всего лишь 1 (довольно простая) отформатированная строка, которую нужно применить.

F. Hauri - Give Up GitHub 02.09.2024 09:56

@F.Hauri А наоборот! Как только вам придется иметь дело с произвольными входными данными (в данном случае это имена путей, которые могут содержать практически любые символы), вам лучше использовать надежный кодировщик JSON, чем заново изобретать механизм, пытаясь вручную подготовиться к каждой возможности. Тем не менее, это не обязательно должен быть jq, вероятно, подойдет любой проверенный процессор JSON.

pmf 02.09.2024 10:05

Совершенно странно хотеть выводить данные в формате JSON, но не устанавливать стандартный инструмент де-факто для обработки JSON.

tripleee 02.09.2024 11:59

У нас есть две операции... 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, если это то, что вы хотите проверить).

Это не приведет к тому, что вместо каталогов будут указаны файлы ответов! Другими словами: будет соответствовать и записываемым файлам.

F. Hauri - Give Up GitHub 02.09.2024 09:43

Неясно, передается ли ОП в каталогах или файлах. Судя по ожидаемому результату, эти пути на самом деле являются каталогами; но в соответствии с принципами Unix это просто возвращает входные данные в качестве выходных. Если вы хотите знать, доступен ли / для записи, вы можете это проверить. Если вы хотите знать, можете ли вы создавать /foo, вы можете это проверить.

tripleee 02.09.2024 09:46

В заголовке вопроса говорится, что проверяется, можно ли успешно создать там файлы. Тогда это не должны быть файлы, фифосы или устройства!

F. Hauri - Give Up GitHub 02.09.2024 11:21

Я правда не уверен. Можно ли создавать файлы в /? Если у вас есть разрешения, вы можете создать /foo.

tripleee 02.09.2024 11:23

Я действительно уверен, что файл не может быть создан в файлах! Вам нужно использовать test -d и test -w! (Что-то вроде test -d "$path" && test -w "$path"; echo $((1-$?)) ;-)

F. Hauri - Give Up GitHub 02.09.2024 11:25

А чтобы избежать проблем, когда $? становится выше 1, вы можете использовать $(( $? ? 0 : 1 ))

F. Hauri - Give Up GitHub 02.09.2024 11:37

Если /foo существует, но у вас есть разрешения на перезапись, скрипт сообщит вам об этом.

tripleee 02.09.2024 11:42

Давайте продолжим обсуждение в чате.

F. Hauri - Give Up GitHub 02.09.2024 11:44

Привет обоим, у меня есть общие ресурсы NFS, смонтированные в различных каталогах системы, и мне нужен способ проверить, не потерялось ли соединение. Поэтому я хочу иметь возможность проверять, что новые файлы могут быть успешно записаны в эти каталоги, где должны появиться монтируемые файлы. Мне не нужно когда-либо проверять файлы напрямую (раньше я создавал только фиктивный файл для проверки того, что каталог присутствует и доступен для записи). Входные данные должны быть только путями к каталогам, и меня не волнует защита от передачи файлов в качестве входных данных.

teeeeee 02.09.2024 16:17

Существует ли при использовании test -w path какая-то потенциальная проблема, поскольку возврат 1 является ложным, а возврат 0 — истинным в bash?

teeeeee 02.09.2024 16:18

Арифметическая оценка $((1-$?)) решает эту проблему. Если $? равен 0, результат равен 1, и наоборот.

tripleee 02.09.2024 17:44

@tripleee, я вижу. хороший. У меня есть символ двоеточия (:) между «путем» и именем пути. У тебя есть запятая. (Хотя между полями «isavailable» и «path» используется запятая. Можете ли вы это исправить? Кроме того, можете ли вы добавить новую строку между каждым каталогом для удобства чтения печати, как в OP?

teeeeee 02.09.2024 22:27

@tripleee Кроме того, я хочу использовать подход touch/rm, потому что использование test -w не дает точного результата, когда каталог является монтированием NFS. Тем не менее, касание по-прежнему показывает, пригоден ли к использованию подключенный диск (он выйдет из строя либо из-за ошибки ввода-вывода, либо зависнет. Последнее можно обнаружить с помощью простой оболочки тайм-аута). Можете ли вы обновить свой ответ, чтобы отразить это, потому что ваш ответ - это почти то, что я ищу с расширением параметров.

teeeeee 02.09.2024 23:06

Другие вопросы по теме