#!/bin/bash IFS=$'\n' years=( $(find. -maxdepth 1 -name '*.jpg' -print0 | sed -zEn 's/^.*_([0-9][0-9][0-9][0-9])-.*\.jpg/\1/p' | tr '\0' '\n' | sort -u) ) for year in "${years[@]}" ; do mkdir -p "$year" find. -iname "*_${year}-*.jpg" -size +1k -print0 | head -z -n 100 | xargs -0r cp -t "$year" done
Это создает массив (
$years
), содержащий уникальный набор из 4 -цифр года, извлеченных из имен файлов в текущем каталоге, где году предшествует символ подчеркивания (_
), а за ним следует тире(-
). Для этого требуется версия GNUsed
для опции-z
, также известной как--null-data
.Для каждого года сначала создается каталог для этого года, если он еще не существует, а затем используется
find
для перечисления всех имен файлов, соответствующих требуемому шаблону, размер которых превышает 1 КБ. Затем этот список передается черезhead
для получения только первых 100 строк, а затем вxargs
для копирования файлов в соответствующий каталог.Список имен файлов завершается NUL -на протяжении всего конвейера, поэтому он работает со всеми допустимыми именами файлов (, т. е. он не прерывается, если в именах файлов есть пробелы, символы табуляции, символы новой строки или другие необычные, но вполне допустимые символы.)
Для этого также требуется GNU-версия
head
(, которая является стандартной для Linux ), потому что она использует опцию-z
(, также известную как--zero-terminated
)для ввода с завершением NUL -. В частности, для этого требуется версия более поздняя, чем 13 января 2016 года . Также требуется GNUcp
для параметра-t
(, также известного как--target-directory
), что позволяет указывать целевой каталог в качестве первого аргумента, а не последнего.Если файлы необходимо отсортировать, то
sort -z
можно вставить между командамиfind
иhead
-, например.find... -print0 | sort -z... | head -z...
. Для этого также требуется версия GNUsort
.Это предполагает, что, как указано в редакции вашего вопроса, имена файлов имеют знак подчеркивания, за которым следует год, как последний элемент перед расширением
.jpg
.Если год может появляться в любом месте имени файла, вам может понадобиться использовать
-iname "*${year}*.jpg"
(без подчеркивания и с секундой*
между${year}
и.jpg
), но следите за файлами, где восемь -цифровое число в начале похоже на60420017
, которое содержит2001
в качестве подстроки.Это также предполагает, что все ваши файлы имеют (регистрозависимые -нечувствительные к регистру)
.jpg
расширения (, а не.jpeg
,.jpe
,.jfif
,.gif
,.png
и т. д. ). Если требуется несколько расширений имени файла, вместо-iname
можно использовать параметр-iregex
.
С одним вызовом awk
и без пайпа:
#! /bin/sh -
string='whatever'
has_char_of_each_class() {
LC_ALL=C awk -- '
BEGIN {
for (i = 2; i < ARGC; i++)
if (ARGV[1] !~ "[[:" ARGV[i] ":]]") exit 1
}' "$@"
}
if has_char_of_each_class "$string" lower upper digit punct; then
echo OK
else
echo not OK
fi
Это POSIX, но учтите, что mawk
пока не поддерживает классы символов POSIX. --
не требуется для совместимых с POSIX awk
, но будет в более старых версиях busybox awk
(, которые будут подавлять значения $string
, начинающиеся с-
).
Вариант этой функции с использованием конструкции оболочки case
:
has_char_of_each_class() {
input=$1; shift
for class do
case $input in
(*[[:$class:]]*) ;;
(*) return 1;;
esac
done
}
Обратите внимание, однако, что изменение локали для оболочки в середине скрипта работает не со всеми sh
реализациями (, поэтому вам нужно, чтобы скрипт вызывался уже в локали C, если вы хотите, чтобы входные данные должны рассматриваться как закодированные в кодировке локали C, а классы символов должны соответствовать только тем, которые указаны в POSIX ).
Это ответ Романа Перехреста, переписанный для работы с mawk:
#!/bin/sh --
string='Aa1!z'
if printf '%s\n' "$string" | LC_ALL=C awk '/[a-z]/ && /[A-Z]/ && /[0-9]/ && /[!-\/:-@[-`{-~]/ {exit 1}'; then
printf '%s\n' 'String does not meet your requirements'
else
printf '%s\n' 'String meets your requirements'
fi
Он также заимствован из ответа bxm, используя код выхода awk вместо проверки того, является ли вывод awk пустым.
Для полноты, поскольку в других ответах не упоминается PCRE. Ограничение BRE/ERE заключается в том, что вы не можете тривиально¹ реализовать логические и для эквивалентного логического «или» в чередовании с |
.
Шаблоны PCRE позволяют создавать условия «и», используя утверждения нулевой -ширины, :смотреть -вперед или смотреть -назад. Они «не потребляют» никаких символов, но ограничивают сопоставление до или после шаблонов. Есть много способов их использования, здесь имеет смысл поместить взгляд -вперед :
.LC_ALL=C pcregrep -q '(?=.*[[:upper:]])(?=.*[[:lower:]])(?=.*[[:digit:]])(?=.*[[:punct:]]).{4,}'
PCRE применяет 4 "предварительных условия" к входным данным перед применением совпадения..{4,}
(4 или более символов, не стесняйтесь увеличивать их; -). Следует отметить, что «(?=[[:upper:]])
» будет проверять только один символ, поэтому каждому условию предшествует «.*
», поэтому проверяется весь ввод.pcregrep
также поддерживает локаль через --locale=C
.
Поскольку «P» в PCRE означаетperl
:
perl -wln -e \
'/(?=.*[[:upper:]])(?=.*[[:lower:]])(?=.*[[:digit:]])(?=.*[[:punct:]]).{4,}/ && exit 0; exit 1;'
делает то же самое для одной строки ввода(это не обычная замена " pcregrep -q
" ).
Надмножество этой задачи с вращением головы -можно найти здесь:https://stackoverflow.com/questions/469913/regular-expressions-is-there-an-and-operator
¹ Вы могли бы расширить ERE, чтобы эмулировать "и" перестановками:
[[:lower:]].*[[:upper:]].*[[:digit:]].*[[:punct:]]|
[[:lower:]].*[[:upper:]].*[[:punct:]].*[[:digit:]]|
[[:lower:]].*[[:digit:]].*[[:upper:]].*[[:punct:]]|... 20 more lines...
[[:punct:]].*[[:digit:]].*[[:upper:]].*[[:lower:]]
Определенно не поможет быть "неэффективным и многословным".
Беззастенчивое воровство у @HaroldFischer @bxm и @RomanPerekhrest за чистое awk
решение
awk -v test="does not meet" '/[a-z]/ && /[A-Z]/ && /[0-9]/ && /[[:punct:]]/ {test="meets"}
END {print "String "test" your requirements"}' <<<"Aa&0"
Теперь, если бы bash
был вариант :Вы можете включить расширенную подстановку и объединить шаблоны @(
и !(
sub -для создания подстановки @(!(*[[:upper:]]*)|!(*[[:lower:]]*)|!(*[[:punct:]]*)|!(*[[:digit:]]*))
для сравнения с
$ shopt -s extglob
$ arr=( '!(*'{'[[:upper:]]','[[:lower:]]','[[:punct:]]','[[:digit:]]'}'*)' )
$ pattern=$(IFS='|'; printf '@(%s)' "${arr[*]}")
$ printf "$pattern\n"
@(!(*[[:upper:]]*)|!(*[[:lower:]]*)|!(*[[:punct:]]*)|!(*[[:digit:]]*))
$ [[ 'Aa3,' = $pattern ]] && echo yes
$ [[ 'Aa3' = $pattern ]] && echo yes
yes
$ [[ 'Aa,' = $pattern ]] && echo yes
yes
$ [[ 'A3,' = $pattern ]] && echo yes
yes
$ [[ 'a3,' = $pattern ]] && echo yes
yes
С гибким awk
сопоставлением с образцом:
if [[ $(echo "$string" | awk '/[a-z]/ && /[A-Z]/ && /[0-9]/ && /[[:punct:]]/') ]]; then
echo "String meets your requirements"
else
echo "String does not meet your requirements"
fi
Следующий сценарий длиннее вашего кода, но показывает, как можно проверить строку на соответствие списку шаблонов. Код определяет, соответствует ли строка всем шаблонам или нет, и выводит результат.
#!/bin/sh
string=TestString1
failed=false
for pattern in '*[[:upper:]]*' '*[[:lower:]]*' '*[[:digit:]]*' '*[[:punct:]]*'
do
case $string in
$pattern) ;;
*)
failed=true
break
esac
done
if "$failed"; then
printf '"%s" does not meet the requirements\n' "$string"
else
printf '"%s" is ok\n' "$string"
fi
Составная команда case... esac
— это способ POSIX для проверки строки на соответствие набору подстановочных шаблонов. Переменная $pattern
используется в тесте без кавычек, поэтому совпадение не выполняется как сравнение строк. Если строка не соответствует заданному шаблону, то она будет соответствовать *
, и цикл будет закрыт после установки failed
на true
.
Выполнение этого приведет к
$ sh script.sh
"TestString1" does not meet the requirements
Тестирование можно спрятать в функцию, например (код проверяет ряд строк в цикле, вызывая функцию):
#!/bin/sh
test_string () {
for pattern in '*[[:upper:]]*' '*[[:lower:]]*' '*[[:digit:]]*' '*[[:punct:]]*'
do
case $1 in ($pattern) ;; (*) return 1; esac
done
}
for string in TestString1 Test.String2 TestString-3; do
if ! test_string "$string"; then
printf '"%s" does not meet the requirements\n' "$string"
else
printf '"%s" is ok\n' "$string"
fi
done
Если вы хотите установить LC_ALL=C
локально в функции, запишите его как
test_string () (
LC_ALL=C
for pattern in '*[[:upper:]]*' '*[[:lower:]]*' '*[[:digit:]]*' '*[[:punct:]]*'
do
case $1 in ($pattern) ;; (*) return 1; esac
done
)
Обратите внимание, что тело функции теперь находится в подоболочке -. Таким образом, установка LC_ALL=C
не повлияет на значение этой переменной в вызывающей среде.
Заставьте функцию оболочки также принимать шаблоны в качестве аргументов, и вы в основном получите ответ Стефана Шазела (вариант).
Вдохновлен Романом Перехрестом, но с некоторыми незначительными уточнениями, чтобы покончить с конвейером и подстановкой команд:
if awk '/[[:lower:]]/ && /[[:upper:]]/ && /[[:digit:]]/ && /[[:punct:]]/ {exit 1}' <<< "$string" ; then
echo "did not match all requirements"
else
echo "looks good to me"
fi
Ниже создается случайный пароль с запрошенным. Вы можете сделать пароль длиннее, заменив head -c 12
большим значением.
while true
do
A=$(head /dev/urandom | tr -dc A-Za-z0-9.\'\"$,_! | head -c 12 ; echo '')
[[ ${A} =~ [A-Z] && ${A} =~ [a-z] && ${A} =~ [0-9] && ${A} =~ [.\'\"$,_!] ]] && break
done