Как заключать в кавычки восклицательные знаки в именах файлов в сценарии оболочки tcsh с помощью find и awk?

"${!x[1]}"— косвенная ссылка, использующая элемент с индексом 1массива x.

$ foo=123; bar=456; x=(foo bar); echo "${!x[1]}"
456

В текущих версиях Bash (4.3 и выше )вы можете использовать namerefs для получения желаемого:

$ a=(00 11 22 33 44)
$ typeset -n y=a
$ echo "${y[3]}"
33

, то есть с установленным nameref, "${y[3]}"является ссылкой на элемент 3в массиве, названном y.


Чтобы перебрать массивы, как вы это делаете в своем вопросе, вы просто сделаете xссылку на имя.

a1=(1 2 3); a2=(a b c)
typeset -n x;
for x in a1 a2; do
    echo "${x[1]}"
done

Присваивания, выполняемые циклом for, изменяют значение самого x(, изменяя то, на что указывает ссылка ). Обычное присваивание(x=123илиx[1]=123)изменяет переменную, на которую в данный момент ссылается x. Таким образом, это изменит как a1[1], так и a2[1]на foo:

.
typeset -n x;
for x in a1 a2; do
    x[1]=foo
done

Причина, по которой "${!x[0]}"работает, заключается в том, что xи x[0]эквивалентны. Если бы у вас было echo "${x[0]}"внутри цикла (без взрыва ), вы получили бы a1, a2, так же, как и с echo "$x".

1
17.06.2021, 17:08
2 ответа

Было бы намного проще просто вызвать mvнапрямую, чем синтезировать команду в awkи передавать ее в оболочку

#!/bin/bash
#
find -mindepth 1 -maxdepth 8 -type f -name '*[ !$]*' -execdir
    bash -c 'for f in "$@"; do echo mv -- "$f" "${f//[ !$]/_}"; done' _ {} +

Эта версия переименовывает файлы, но не каталоги (, как указано в вопросе ). Если вы действительно хотели переименовать каталоги, используйте этот вариант,

find -mindepth 1 -maxdepth 8 -depth -name '*[ !$]*' -execdir
    bash -c 'for f in "$@"; do echo mv -- "$f" "${f//[ !$]/_}"; done' _ {} +

В обоих случаях удалите echo, если вы уверены, что команда делает то, что вы ожидаете. Используйте mv -iдля интерактивного переименования. Подчеркивание в bash -c '...' _ {}становится «именем» кода, работающего в подоболочке. Он сопоставлен с $0и будет использоваться для сообщения об ошибках кода (, если таковые имеются ), так что вы можете поместить туда bashили даже subshell-, это просто метка.

1
28.07.2021, 11:26

Этот скрипт предполагает, что вы не хотите ограничивать глубину для findи хотите заменить все символы $!"'на _.

Поскольку вопрос не содержит информации об операционных системах или конкретных версиях инструментов, я стараюсь полагаться только на спецификацию POSIX и не требовать конкретных версий, таких как GNU.

#!/bin/sh
find. -depth -name '*[ $!"]*' | while IFS= read -r file
do
   dir="${file%/*}"
   newname="$(echo "${file##*/}" | tr ' $!"' '____')"
   echo mv "$file" "$dir/$newname"
done

Скрипт протестирован незначительно. Если он печатает правильные команды mv, удалите echo.

Пояснение:

find. -depth... гарантирует, что файлы печатаются перед каталогом, если оба содержат заменяемые символы. Таким образом, мы сначала переименуем файлы, а затем родительский каталог.

Имя родительского каталога отделяется dir="${file%/*}" `и остается без изменений.

Имя файла (или последнее имя каталога )извлекается и изменяется с помощью newname="$(echo "${file##*/}" | tr ' $!"' '____')".


Дальнейшие улучшения

Для обработки других «странных» символов в именах файлов лучше вызывать код в теле цикла непосредственно из действия find's -exec, как в roaima 's ответ .

find. -depth -name '*[ $!"]*' -exec sh -c 'for file in "$@" ; do dir="${file%/*}" ; newname="$(echo "${file##*/}" | tr '\'' $!"'\'' "____")" ; echo mv "$file" "$dir/$newname"; done' _ {} +

См. такжеhttps://mywiki.wooledge.org/BashFAQ/020

Использование -execdir, как и в другом ответе, позволит избежать разделения имени файла и родительского каталога, но это действие не определено POSIX.

Можно заменить комбинацию echo... |tr... заменой оболочкой, используя что-то вроде "${newname//[ $!\"]/_}".

0
28.07.2021, 11:26

Теги

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