Чтение файла с помощью цикла IFS работает только тогда, когда массивы не используются

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

gfind . -mindepth 1 -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh

Но на самом деле не делайте этого. никогда не вставляйте вещи в sh . Это не работает , если имена ваших файлов не содержат специальных символов оболочки. Если у вас есть файл с именем `rm -r ~` (вполне допустимое, хотя и необычное имя файла), то pop переходит в ваш домашний каталог. Вызов оболочки с помощью -exec , как показано ниже.

Вторая попытка не сработает, потому что вы вставляете _ в начало полного пути, а не добавляете его в начале базового имени, то есть после последнего / .

find . -mindepth 1 -type d -exec rename 's![^/]+$!_$&!' {} \;

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

find . -mindepth 1 -depth -type d -exec rename 's![^/]+$!_$&!' {} \;

Для большей переносимости вызовите оболочку и запустите mv . Это работает в любой системе POSIX.

find . -depth -type d -name . -o -exec sh -c '
    for d do mv "$d" "${d%/*}/_${d##*/}"; done
' sh {} +

Если у вас нет вложенных подкаталогов, вы можете использовать простой цикл оболочки. Если вы не хотите переименовывать каталоги, имена которых начинаются с точки, просто используйте подстановочный знак * / , который соответствует только каталогам и символическим ссылкам на каталоги.

for d in */; do
  if [ "$d" = "*/* ] && [ ! -e "$d" ]; then continue; fi   # robustness in case there are no subdirectories
  if [ -L "$d" ]; then continue; fi     # optional: skip symbolic links to directories
  mv -- "$d" "_${d%/}"
done

Если вы хотите переименовать все каталоги, включая те, чьи имена начинаются с точки, добавьте также подстановочный знак . * .

for d in .*/ */; do
  if [ "$d" = "*/* ] && [ ! -e "$d" ]; then continue; fi
  if [ -L "$d" ]; then continue; fi
  mv -- "$d" "_${d%/}"
done

Обратите внимание, что когда вы перемещаете foo в _foo , если уже существует каталог с именем _foo , это перемещает старый foo в _foo / foo . Чтобы защититься от этого, добавьте явный тест.

Или используйте zsh. Он предустановлен на macOS. Сначала запустите autoload -U zmv (поместите это в свой .zshrc или в вашем скрипте), чтобы загрузить функцию zmv , затем нерекурсивную версию (включая точечные файлы, исключая символьные ссылки - (/ D) - это набор of квалификаторов glob ):

zmv -Q '*(/D)' '_$f'

или рекурсивная версия (с использованием модификаторов для извлечения имени каталога и базового имени):

zmv -Q '**/*(/D)' '$f:h/_$f:t'

zmv откажется выполнять перемещение, если цель уже существует.

2
29.06.2016, 22:31
2 ответа

У вас уже есть ответ, в котором говорится, как это сделать полностью в bash с помощью цикла while ... read ... ... Я бы хотел предложить, чтобы сделать это только в bash - одно из худших способов сделать это.

Кроме того, чтение / etc / group само по себе не является хорошей идеей, потому что этот файл является лишь одним из возможных источников данных группы (другие источники могут включать LDAP, Active Directory, winbind, NIS, mysql , pgsql, файлы базы данных Berkeley, файлы дополнительных групп из libnss-extrausers и многие другие).

Вместо этого используйте вывод getent group .

Попробуйте следующее:

g=( $(getent group | awk -F: '{print $3}') )

или

g=( $(getent group | cut -d: -f3) )

Метод awk особенно полезен, если вы хотите объединить его с регулярным выражением или поиском с фиксированным текстом и / или другими критериями выбора (например, $ 1 == groupname или $ 3> = 500 && $ 3 <= 1000 )

Кстати, например, для отладки вы можете распечатать группы в массиве g , например:

# all on one line
echo "${g[@]}"

# one element per line
printf "%s\n" "${g[@]}"

# in a format that can be re-used to define the variable in another
# bash shell or script.
declare -p g

Мне особенно нравится declare -p для отладки - он не только показывает имя переменной (например, похоже на echo "foo = '$ foo'" ), но также показывает индексы массива для переменных массива, и все правильно заключено в кавычки и экранировано обратной косой чертой:

$ g=( $(getent group | awk -F: '$1 ~ /my/ {print $1}') )
$ declare -p g
declare -a g='([0]="mysql" [1]="mythtv")'

BTW typeset является синонимом declare , который работает в bash, а также в некоторых других оболочках, таких как ksh .

0
27.01.2020, 21:54

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

GROUPS

Переменная массива, содержащая список групп, членом которых является текущий пользователь. является текущий пользователь. Присвоение переменной GROUPS не имеет никакого эффекта и возвращают статус ошибки. Если переменная GROUPS не установлена, она теряет свои особые свойства, даже если впоследствии он будет сброшен.

Просто используйте другое имя, и код должен работать.

6
27.01.2020, 21:54

Теги

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