Как отфильтровать аргументы скрипта и сохранить их в новый файл?

Я работаю над написанием bash-скрипта или команды, которая будет:

  • Сохранить в файл X из его аргументов, которые являются каталогами, содержащими не более 20 файлов.
  • Имя файла, представленное как X, должно быть набрано с клавиатуры.
  • Файл X должен содержать названия каталогов и их разрешения.

Я начал с поиска каталогов, содержащих не более 20 файлов, таким образом:

find . -maxdepth 1 -type d -exec bash -c "echo -ne '{} '; ls '{}' | wc -l" \; | awk '$NF<=20'

Но у меня возникают проблемы с сохранением аргументов в конкретном файле.

1
задан 24 June 2019 в 21:57

1 ответ

Запись вывода в файл, имя которого было обеспечено в интерактивном режиме пользователем

Вы сказали наличие затруднений при сохранении в конкретный файл, имя которого обеспечивается пользователем сценария, и Вы подчеркнули желание справки с этим конкретно. Но Вы не упоминали проблемы, предлагающей пользователю ввести имя файла. Таким образом, я предполагаю, что у Вас есть та работа, и что Вы делаете что-то вроде IFS= read -r outfile, где outfile название переменной, которая будет содержать обеспеченное пользователями выходное имя файла.

Если вывод записи в файл, имя которого сдержано outfile все, что Вы оставили, чтобы сделать, затем Вы уже быть очень близко к Вашей цели. Замена outfile с любым именем переменной Вы выбрали...

Это могло бы быть всем, в чем Вы нуждаетесь. Но существуют некоторые другие модификации, которые Вы могли бы рассмотреть.

Можно использовать find и сделайте большую часть того, без чего Вы нуждаетесь -exec.

Как steeldriver говорит от Ваших установленных целей, похоже, что Вы не хотите, чтобы Ваш сценарий рассмотрел все файлы в текущем каталоге, как find . -maxdepth 1 ... делает, но вместо этого только полагайте, что аргументы передали Вашему сценарию.

Однако, может быть разумно использовать find для этого и одного преимущества выполнения так то, что, если действительно необходимо изменить его для работы рекурсивно позже, который будет очень легок. (С другой стороны, часто неблагоразумно предпринять дополнительное усилие подготовиться к опциям, которые Вы, возможно, должны были бы добавить позже, так как трудно предсказать, каково функции это на самом деле будет.)

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

Если Вы хотите использовать find, можно передать аргументы сценарий, полученный к find как поддерживает его поиск вместо того, где Вы в настоящее время писали ., и используйте -maxdepth 0 таким образом, это на самом деле не убывает в них. Это сделало бы неправильную вещь, если некоторые аргументы имени файла Вашему сценарию запускаются с -, так как они могли быть интерпретированы особенно find (как опции, предикаты или действия). Но большинство команд рассматривает споры с продвижением - символы особенно; обычно достаточно зарегистрировать это поведение для других пользователей и гарантировать, чтобы Вы случайно не передавали такие пути к своему сценарию. (Если Вы действительно хотите считать запись в текущем каталоге названной как -foo, можно передать его имя как ./-foo вместо этого.)

Для простоты я сначала рассмотрю, как решить более легкую проблему вывода строк для всех аргументов, переданных сценарию, которые являются названиями каталогов, неважно, сколько записей они содержат (т.е. игнорирование "не больше, чем 20 файлов" требование).

find "$@" -maxdepth 0 -type d -printf '%M %p\n' 2>/dev/null >"$outfile"

Вот то, как это работает:

  • "$@" расширяется до списка позиционных параметров всего сценария. (Параметры командной строки, переданные Вашему сценарию, становятся значениями его позиционных параметров $1, $2, $3, и т.д.) Каждый будет передан find как отдельный аргумент. Это работает, даже если некоторые имена содержат пробелы.
  • -maxdepth 0 говорит find для не взгляда немного глубже, чем пути, Вы явно передали его. Поэтому это будет только полагать, что параметры командной строки передали Вашему сценарию.
  • -type d находит только каталоги, когда Вы использовали его в своей команде.
  • -printf '%M %p\n' печать ls- разработайте символьную строку полномочий (%M), пространство, имя файла (%p), и концы строка (\n). См. документацию -printf действие в man find.
  • Я не уверен из Вашего описания, должен ли Ваш сценарий считать это нормальным, чтобы пользователь передал аргументы, которые даже не определяют доброго файла вообще, каталога или иначе. Так, вместо того, чтобы составлять требования, которые не могли бы относиться к Вам, я пошел с быстрым и грязным подходом подавления всех сообщений об ошибках от find с 2>/dev/null. Это перенаправляет стандартную погрешность (дескриптор файла 2) к /dev/null (специальное "устройство", которое выбрасывает его вход). Если Вы хотите видеть сообщения об ошибках, когда аргументы передали Вашему сценарию, не называют ничего вообще, просто опускают эту часть. (Сценарий все еще сделает свое задание, даже при том, что сообщения об ошибках отображены. Таким образом, они - эффективно предупреждения, а не ошибки.), Если Вы хотите некоторое другое поведение, можно работать над этим и/или дать больше информации о требованиях. Я называю это "быстрым и грязным" подходом, потому что это возможно для find для издания других сообщений об ошибках и предупреждающих сообщений, и они также были бы подавлены.
  • >"$outfile" записи к выходному файлу, имя которого сдержано outfile переменная, как описано выше.

Одна вещь рассмотреть - то, что происходит, если пользователь не передает аргументов. Затем find не видит корней для запуска с. Когда это происходит, это ведет себя, как будто Вы передали .. (Не все find реализации рассматривают отсутствие корней как подразумеваемое ., но GNU находит, реализация находки в Ubuntu, делает.) Необходимо обработать это..., если Вы не хотите того поведения.

Фильтрация каталогов, содержащих больше чем 20 файлов

Разумно использовать -exec для вызова другой команды, которая будет считать файлы в каждом каталоге, и подход к этому на правильном пути. Но это должно пойти перед -printf действие (или безотносительно другого действия Вы используете для причины find производить имена файлов), так, чтобы это действие только произошло для каталогов, которые проходят тест.

Если Вы принимаете решение считать файлы путем подсчета сколько строк вывода ls команда производит, затем это ls команда должна быть вызвана с -q опция. Это вызвано тем, что имена файлов могут содержать странные символы, которые Вы не могли бы ожидать, включая символы новой строки. -q заменяет такие символы ?s, который во многих целях был бы несоответствующим, но так как Вы на самом деле не смотрите на имена, это прекрасно. (Можно также хотеть использовать -1 опция, потому что, хотя это избыточно когда ls выводы к каналу (как здесь), это ясно документирует Ваше намерение это ls испустите одну строку на файл.) В целом необходимо постараться не анализировать ls, но так как Вы не заботитесь о фактических именах файлов и -q гарантирует, чтобы Вы получили одну строку на файл, это хорошо.

Но я покажу другую технику вместо этого. Можно поместить этот тест после -type d но прежде -printf:

-exec bash -c 'a=("$1"/*); ((${#a[@]} <= 20))' _ {} \;

bash -c ... использование a bash оболочка. Я записал bash вместо sh потому что существует a bash функция я хочу использовать: массивы. Выполнения оболочки a=("$1"/*); ((${#a[@]} <= 20)). В первой команде, a=("$1"/*):

  • "$1" расширяется до первого позиционного параметра, который соответствует имени файла find передачи вместо {}. Это - название обрабатываемого каталога.
  • Запись "$1"/* расширяется до путей файлов, содержавшихся в том каталоге, опуская тех, имена которых запускаются с ., как ls команда сделала. (Если Вы хотите считать их также, можно записать shopt -s dotglob; прежде a=....)
  • Заключение в скобки его на правой стороне переменного присвоения заставляет его быть сохраненным в переменной типа массив.

Массив называют a и его размер может позже быть получен путем записи ${#a[@]}. Вторая команда, ((${#a[@]} <= 20)), использует в своих интересах это:

  • (( )) оценивает арифметические выражения и возвращается верный / успех, если они являются ненулевыми, ложь/отказ, если они - нуль.
  • Как упомянуто выше, ${#a[@]} расширяется до размера массива, который является количеством записей в каталоге find переданный вместо {}. Это - то, что необходимо сравнить с 20.
  • <= 20 сравнивает его с 20.

Можно попытаться экспериментировать с другими значениями, чем 20, чтобы гарантировать, что это работает правильно.

Соединение всего этого

Полный сценарий мог выглядеть примерно так (но помните, я не знаю всех Ваших требований, и это предназначается намного больше как демонстрация, чем как решение):

#!/bin/sh

if [ "$#" -eq 0 ]; then
    printf '%s: warning: no arguments, behaving as "%s ."\n' "$0" "$0"
fi

printf 'Output filename> '
IFS= read -r outfile

find "$@" -maxdepth 0 -type d \
         -exec bash -c 'a=("$1"/*); ((${#a[@]} <= 20))' _ {} \; \
         -printf '%M %p\n' 2>/dev/null >"$outfile"

Несколько соображений:

  • Пользователь, который запускает скрипт, должен быть осторожным, что они вводят как выходное имя файла. Если тот файл будет существовать, то он будет перезаписан.
  • Я шел вперед и продемонстрировал один возможный способ иметь дело со случаем никаких аргументов: продолжение, но предупреждение пользователя, который это - как будто они запустили скрипт и передали ..
  • Хотя команда оболочки, выполненная от find -exec действие полагается bash для массива, больший сценарий, который читает ввод данных пользователем и вызовы find не использует никакие подобные функции. Можно выполнить его с bash если Вам нравится, и можно изменить hashbang на #!/bin/bash. Но я пошел голова и записал это как #!/bin/sh, поскольку это должно быть достаточно.

Я назвал свой файл сценария lt20. Можно назвать его вообще, Вам нравится, но необходимо сделать это исполняемым файлом путем выполнения chmod +x lt20 (но с любым именем Вы действительно давали его). Вот то, на что это похоже для использования сценария с текстом, введенным пользователем, показанным курсивом:

$ ./lt20 /usr/*
Output filename> out
$ cat out
drwxr-xr-x /usr/arm-linux-gnueabi
drwxr-xr-x /usr/arm-linux-gnueabihf
drwxr-xr-x /usr/games
drwxr-xr-x /usr/lib32
drwxr-xr-x /usr/libexec
drwxr-xr-x /usr/local
drwxr-xr-x /usr/src

Моя машина имеет двенадцать подкаталогов /usr, и они были все переданы как аргументы сценарию, но только те, которые имеют 20 или меньше записей (другой, что записи, которые запустились с .) были перечислены. У всех них, оказалось, были те же полномочия, drwxr-xr-x, но сценарий только показывает ту же строку разрешения для каждой записи, когда у них на самом деле есть те же полномочия.

0
ответ дан 24 June 2019 в 21:57

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

Похожие вопросы: