"${!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"
.
Было бы намного проще просто вызвать 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
-, это просто метка.
Этот скрипт предполагает, что вы не хотите ограничивать глубину для 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//[ $!\"]/_}"
.