Исключить из * в командной строке

14

Существует множество ситуаций, когда использование a *практически неизбежно, например, rm -rf *в папке, содержащей тысячи вложенных папок и файлов.

Но что, если вы хотите исключить из команды только один или два файла или папки rm? Я гуглил свой путь и нашел только довольно сложные решения, find . -depth -not \( -name 'one' -o -name 'two' \ -o -name 'three' \) -exec rm {} \;как указано здесь .

Есть ли возможность сделать это проще - без этого обхода find? Например, rm -rf --exclude='one' --exclude='two' --exclude='three' *как в Rsync или просто rm -rf -e 'one','two','three' *?

Может быть , даже вообще возможность исключить вещи из *(так что другие команды типа cp, mv... не должны реализовывать свои собственные)? Что-то вроде *{'one','two','three'}или так?

Дэвид
источник
3
Если вы хотите сохранить только один или два файла, то самым простым и простым способом было бы переместить эти файлы в другое место, стереть все оставшиеся файлы и переместить те, которые вы сохранили обратно. Если вы хотите сохранить гораздо больше файлов, я бы использовал findэту --deleteопцию (не нужно выполнять rmдля каждого файла. Это ненужные накладные расходы).
Хеннес
Это было бы возможно, но это почти так же сложно, как тот, который я упомянул. Я знаю, что мог бы объединить команды в нечто подобное mv -t /tmp one two three && rm -rf * && mv -t . /tmp/one /tmp/two /tmp/three, но я бы предпочел решение, дающее возможность явно исключать что-либо из *. Безусловно, будут ситуации, когда перемещение или копирование файлов в другое место назначения будет невозможным.
Дэвид
2
Именно поэтому я не опубликовал его в качестве ответа. Просто как менее сложный способ действий (три очень простые команды против одной несколько более сложной). Я ненавижу сложность в сочетании с рм.
Hennes

Ответы:

33

Для Bash есть опция оболочки, extglobкоторая по умолчанию отключена, чтобы сохранить совместимость со стандартным синтаксисом оболочки. Extglob дополняет синтаксис дополнительными операторами , как !(), ?(), @()и некоторые другие.

Чтобы включить extglob, введите shopt -s extglob. Чтобы он был включен для текущего типа пользователя echo 'shopt -s extglob' >> ~/.bashrc.

Для примера rm: С extglobвы можете использовать

rm -rf !(one|two|three)
choroba
источник
Это похоже на хорошее решение, но я также не хочу включать и выключать его каждый раз, когда попадаю в ситуацию, подобную описанной.
Дэвид
3
@ Дэвид: вы можете положить shoptвещь в свой ~/.bashrc, это не повредит.
энзотиб
Если я правильно понял, опция extglob вообще не меняет эффекта *, а вместо этого добавляет аналогичные индикаторы (например, !), которые имеют дополнительные функции? В таком случае это было бы прекрасным решением.
Дэвид
1
@ Дэвид: Точно.
Чороба
1
@David Для сохранения совместимости со стандартным синтаксисом оболочки . Вероятно, в других ситуациях поведение может измениться.
геррит
4

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

У меня есть папка, подобная следующей:

$ ls
a b c d

Печатание except b dдаст вам aиc

$ except b d
ac

Теперь вы можете передать это rm.

except b d | xargs -0 rm

Это может быть трудно запомнить, так почему бы просто не создать скрипт поверх, кроме называемого rm-except:

#!/usr/bin/env bash

except "$@" | xargs -0 rm

Точно так же легко это ls-except:

#!/usr/bin/env bash

except "$@" | xargs -0 ls
Вспомогательный метод
источник
3
Это хорошо, но на самом деле не нужно, когда вы знаете о extglob - askubuntu.com/a/329233/138369
Дэвид
1

В качестве альтернативы extglob (хотя это очень хороший ответ, и каждый должен иметь shopt -s extglob globstarв своем .bashrc), вы можете использовать глобальную переменную $ GLOBIGNORE . Допустим, вы хотите получить все файлы, кроме «foo.txt» и «bar baz.txt»:

GLOBIGNORE=foo.txt:'bar baz.txt'

... однако, это включит параметр оболочки dotglob, что означает, что *будут совпадать файлы, начинающиеся с точки (которые обычно скрыты). Таким образом, вам на самом деле нужны две команды:

GLOBIGNORE=foo.txt:'bar baz.txt'
shopt -u dotglob

Так как это глобальная переменная, она будет влиять на каждый глобус, который вы используете, до тех пор, пока $ GLOBSTAR не будет сброшен при выходе или

GLOBIGNORE=

Он также будет работать только с литеральными строками, переданными в переменную. Вы можете понять, что я имею в виду, установив $ GLOBIGNORE и посмотрев на разницу между этими командами:

printf '%s\n' *
printf '%s\n' ./*
evilsoup
источник