Рассматривать:

$ ls
foo  xyfooz.tex
$ find . -maxdepth 1 -type f -name '*foo*' -exec mv {} foo \;
$ ls ./foo*
xyfooz.tex

Я пытаюсь добиться той же последовательности инструкций только с помощью mv, а не найти:

$ ls
foo  xyfooz.tex
$ mv *foo* foo
mv: cannot stat '*foo*': No such file or directory
$ ls ./foo*
xyfooz.tex

Дополнительно (изменить),

$ ls -F
foo/  xyfooz.tex*
$ mv -v *foo* foo

mv: cannot move 'foo' to a subdirectory of itself, 'foo/foo'
'xyfooz.tex' -> 'foo/xyfooz.tex'

Как сделать так, чтобы предупреждение «stat» не появлялось. Другими словами, я хотел бы уточнить шаблон, чтобы ограничить исходный файл файлами, а не каталогами?

GNU bash, 4.3.48(1)-релиз (x86_64-pc-linux-gnu)

$ cat /etc/os-release
NAME="Linux Mint"
VERSION="18.3 (Sylvia)"

1 ответ1

1

С mv

cannot move 'foo' to a subdirectory of itself в этом случае безвреден, вы можете игнорировать его. Я имею в виду, что mv переместит остальных, несмотря на предупреждение. Однако статус выхода не будет равен 0 , это может быть серьезным препятствием в сценариях, где вы хотите прервать или предпринять корректирующее действие, если mv не удается.

Об этом уже говорилось в комментарии: вы можете отключить mv , перенаправив stderr с помощью 2>/dev/null ; другие предупреждения или ошибки также будут перенаправлены.

Я думаю, что вы не можете легко «уточнить шаблон, чтобы ограничить источник файлами, а не каталогами». Тем не менее вы можете использовать подход, который не соответствует буквальному foo . Следующий подход не имеет ничего общего с файлами или каталогами; только с именами, потому что глобусы имеют дело с именами; так что в некоторых случаях этого может быть недостаточно.

*foo* соответствует четырем дизъюнктивным типам объектов:

  1. сам foo
  2. foo только с префиксом, как bar-foo - вы можете сопоставить их с помощью *?foo
  3. foo с префиксом и постфиксом, например bar-foo-baz - *?foo?*
  4. foo только с постфиксом, как foo-baz - foo?*

Чтобы исключить foo вам нужны только последние три (2-4), поэтому первый подход может быть следующим:

mv *?foo *?foo?* foo?* foo/

Если у вас не установлена опция оболочки nullglob , любой шаблон должен соответствовать чему-либо, иначе он будет передан в mv буквально. Ты не хочешь этого. Решение состоит в том, чтобы заранее выполнить следующую команду:

shopt -s nullglob

Эта опция оболочки заставит любой непревзойденный шар расширяться до нуля. Я советую вам также изучить вариант dotglob .

Обратите внимание, что *?foo* соответствует (2-3). Точно так же *foo?* спички (3-4). Дополнительно учтите -- сообщить mv что все следующие аргументы не являются опциями. Таким образом, файл, подобный --foo , не будет интерпретироваться как (неизвестный) параметр. Это приводит к двум лучшим командам:

mv -- *?foo* foo?* foo/

или же

mv -- *?foo *foo?* foo/

Неважно, какой вы выберете. Просто не используйте mv *?foo* *foo?* foo/ ; он передаст (например, bar-foo-baz в mv дважды (то есть как два отдельных аргумента), что приведет к ошибке, когда инструмент попытается переместить этот объект во второй раз.

Когда совпадение не найдено, мои команды mv вырождаются в mv foo/ и выдают ошибку. Ваша оригинальная команда mvnullglob ) получит литерал *foo* и выдаст еще одну ошибку.


Пример сеанса консоли:

$ shopt -s nullglob
$ mkdir foo
$ touch bar-foo bar-foo-baz foo-baz xyfooz.tex ./--foo
$ mv -v -- *?foo* foo?* foo/
'bar-foo' -> 'foo/bar-foo'
'bar-foo-baz' -> 'foo/bar-foo-baz'
'--foo' -> 'foo/--foo'
'xyfooz.tex' -> 'foo/xyfooz.tex'
'foo-baz' -> 'foo/foo-baz'
$ 

Простой взлом

Допустим, dummy не существует.

mv foo dummy
mv *foo* dummy/
mv dummy foo

Переименование каталога должно быть очень быстрым в файловых системах на основе inode. Программы, имеющие файлы из foo/ уже открытые, не должны ни мешать, ни ломаться, потому что важен именно inode. Однако если какой-либо программе нужно открыть файл (по пути, включая foo/) между первым и третьим mv , это не удастся. Могут возникнуть другие побочные эффекты (вообразите стороннюю программу, воссоздающую foo/ в то же время), поэтому я называю этот подход хаком. Подумайте дважды, прежде чем использовать его.


В любом случае, find

Различение файлов из каталогов - это работа для find . То, что вы сделали с find в основном верный путь. То, что вы хотите сделать (и то, что я делал выше) с помощью mv и shell-шаров, кажется… ну, в большинстве случаев "менее правильным". Это ваша команда:

find . -maxdepth 1 -type f -name '*foo*' -exec mv {} foo \;

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

find . -maxdepth 1 -type f -name '*foo*' -exec mv -t foo/ -- {} +

Преимущества перед вашей командой find и / или над простым mv с glob(s):

  • один mv может получить несколько файлов для работы;
  • тем не менее, если для одной командной строки слишком много файлов для обработки, find запускает дополнительный процесс (ы) mv ;
  • в случае, когда ни один файл не соответствует шаблону, mv вообще не будет запускаться;
  • благодаря -- такой файл, как --foo , не будет интерпретироваться как (неизвестная) опция для mv ;

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .