Как убедиться, что строка содержит хотя бы одну заглавную букву, одну строчную букву, одну цифру и один знак пунктуации?

#!/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 -цифр года, извлеченных из имен файлов в текущем каталоге, где году предшествует символ подчеркивания (_), а за ним следует тире(-). Для этого требуется версия GNU sedдля опции -z, также известной как --null-data.

Для каждого года сначала создается каталог для этого года, если он еще не существует, а затем используется findдля перечисления всех имен файлов, соответствующих требуемому шаблону, размер которых превышает 1 КБ. Затем этот список передается через headдля получения только первых 100 строк, а затем в xargsдля копирования файлов в соответствующий каталог.

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

Для этого также требуется GNU-версия head(, которая является стандартной для Linux ), потому что она использует опцию -z(, также известную как--zero-terminated)для ввода с завершением NUL -. В частности, для этого требуется версия более поздняя, ​​чем 13 января 2016 года . Также требуется GNU cpдля параметра -t(, также известного как --target-directory), что позволяет указывать целевой каталог в качестве первого аргумента, а не последнего.

Если файлы необходимо отсортировать, то sort -zможно вставить между командами findи head-, например. find... -print0 | sort -z... | head -z.... Для этого также требуется версия GNU sort.

Это предполагает, что, как указано в редакции вашего вопроса, имена файлов имеют знак подчеркивания, за которым следует год, как последний элемент перед расширением .jpg.

Если год может появляться в любом месте имени файла, вам может понадобиться использовать-iname "*${year}*.jpg"(без подчеркивания и с секундой *между ${year}и .jpg), но следите за файлами, где восемь -цифровое число в начале похоже на 60420017, которое содержит 2001в качестве подстроки.

Это также предполагает, что все ваши файлы имеют (регистрозависимые -нечувствительные к регистру).jpgрасширения (, а не .jpeg, .jpe, .jfif, .gif, .pngи т. д. ). Если требуется несколько расширений имени файла, вместо -inameможно использовать параметр -iregex.

5
20.11.2019, 03:57
9 ответов

С одним вызовом 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 ).

7
27.01.2020, 20:31

Это ответ Романа Перехреста, переписанный для работы с 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 пустым.

3
27.01.2020, 20:31

Для полноты, поскольку в других ответах не упоминается 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:]]

Определенно не поможет быть "неэффективным и многословным".

0
27.01.2020, 20:31

Беззастенчивое воровство у @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"
1
27.01.2020, 20:31

Теперь, если бы 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
0
27.01.2020, 20:31

С гибким 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
7
27.01.2020, 20:31

Следующий сценарий длиннее вашего кода, но показывает, как можно проверить строку на соответствие списку шаблонов. Код определяет, соответствует ли строка всем шаблонам или нет, и выводит результат.

#!/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не повлияет на значение этой переменной в вызывающей среде.

Заставьте функцию оболочки также принимать шаблоны в качестве аргументов, и вы в основном получите ответ Стефана Шазела (вариант).

3
27.01.2020, 20:31

Вдохновлен Романом Перехрестом, но с некоторыми незначительными уточнениями, чтобы покончить с конвейером и подстановкой команд:

if awk '/[[:lower:]]/ && /[[:upper:]]/ && /[[:digit:]]/ && /[[:punct:]]/ {exit 1}' <<< "$string" ; then
  echo "did not match all requirements"
else
  echo "looks good to me"
fi
3
27.01.2020, 20:31

Ниже создается случайный пароль с запрошенным. Вы можете сделать пароль длиннее, заменив 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
0
04.11.2020, 11:07

Теги

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