
Я хочу удалить файлы, которые имеют size = 0
. Поэтому я попробовал:
find ./ -size 0 | xargs rm
Однако возникают проблемы с файлами, имена которых начинаются с пробела.
Поискав в интернете, я нашел это:
find ./ -size 0 -exec rm -i {} \;
Это работает. Однако, я думаю, что мой способ использования xargs
слишком сложен для этого.
Что {} \;
значит?
Может ли кто-нибудь мне объяснить?
Мой английский не очень хорош, поэтому, пожалуйста, пишите простыми словами.
решение1
{}
не имеет абсолютно никакого значения bash
, поэтому передается без изменений как аргумент выполняемой команды, здесь find
.
С другой стороны, ;
имеет определенное значение для bash
. Обычно он используется для разделения последовательных команд, когда они находятся в одной командной строке. Здесь обратная косая черта в \;
используется именно для того, чтобы предотвратить интерпретацию точки с запятой как разделителя команд, bash
а затем разрешить ее передачу в качестве параметра базовой команде, find
. Заключение точки с запятой в кавычки, то есть ";"
или ';'
, могло бы быть альтернативным способом оставить ее необработанной.
Команда:
find ./ -size 0 -exec rm -i {} \;
означает: найти в текущем каталоге (обратите внимание, что /
здесь бесполезен, .
в любом случае не может быть ничем иным, как каталогом) все, что имеет размер 0, и для каждого найденного объекта выполнить команду , rm -i name
т. е. интерактивно запросить для каждого файла, если вы хотите его удалить. {}
заменяется каждым именем файла, найденным в выполненной команде. Одной из приятных особенностей является то, что это имя файла является строго одним аргументом, каким бы ни было имя файла (даже содержащим пробелы, табуляции, переводы строк и любые другие символы). Это не относится к xargs
, если только не используются непереносимые хаки. final ;
находится там, чтобы закончить -exec
предложение. Причина, по которой его конец должен быть разделен, заключается в том, что за ним find
могут следовать другие параметры -exec
, хотя это делается редко. например:
find . -name "*.js" -exec ls -l {} \; -name "special*" -exec wc -l {} \;
Одна из проблем с этой командой заключается в том, что она не игнорирует не простые файлы, поэтому может предложить пользователю удалить сокеты, блочные и символьные устройства, каналы и каталоги. С последним всегда будет ошибка, даже если вы ответите «да».
Другая проблема, хотя и не очень критичная здесь, заключается в том, что rm
будет вызываться для каждого файла, имеющего нулевой размер. Если вы замените -exec
окончание с /;
на +
, find
оптимизирует создание подпроцесса, вызывая только rm
минимально возможное количество раз, часто всего один раз.
Вот как я бы изменил эту команду:
find . -type f -size 0 -exec rm -i {} +
решение2
При использовании find -exec
расширяется {}
до каждого найденного результата.
Например, если у вас есть каталог, example
содержащий 3 файла a.txt
, b.txt
и c.txt
, find example/ -exec rm -i {} \;
будет расширен до:
find example/ -exec rm -i example/a.txt \;
find example/ -exec rm -i example/b.txt \;
find example/ -exec rm -i example/c.txt \;
В \;
конце просто экранируется, ;
чтобы обозначить конец шаблона exec. В противном случае он будет интерпретирован самой оболочкой.
решение3
В сочетании с опцией find
команды часть заменяется на имя файлов, найденных при выполнении команды. Это также важно, поскольку это то, что определяет конец выполняемой командыexec
{}
\;
Например
find ~ -name \*.bak -exec -rm -f {} \;
удалит все файлы, заканчивающиеся на .bak
в любом месте домашнего каталога пользователя или в папках, содержащихся внутри него. Выполняя rm -f
для каждого найденного файла.
xargs
принимает стандартные входные строки, обычно из конвейера, и формирует из них хвостовую часть аргументов, когда он выполняет команду, которую вы ему даете
решение4
Это старый вопрос, но я хочу добавить немного информации:
find ./ -size 0 -exec rm -i {} \;
В предыдущей команде \;
это экранированная точка с запятой. Это предотвращает обработку команды оболочкой (т.е. обычно ;
разделяет команды).
Аргумент -exec
интерпретирует все как команду до этой экранированной точки с запятой \;
(т.е. rm -i {}
это будет внутренняя команда, которая будет выполнена find
). Внутри внутренней команды {}
представляют собой расширение параметра. На языке pain это означает «вставьте имя файла, найденное здесь».
Таким образом, если найденные файлы были «file-a.txt» и «file-b.txt», то find
будет выполнен .rm -i file-a.txt
rm -i file-b.txt
Одна из проблем этой команды заключается в том, что она не игнорирует не простые файлы, поэтому может предложить пользователю удалить сокеты, блочные и символьные устройства, каналы и каталоги. С последним всегда будет ошибка, даже если вы ответите «да» (т. е. каталоги нужно удалять рекурсивно)
Другая проблема, хотя и не очень критичная здесь, заключается в том, что rm
будет вызываться для каждого файла, имеющего нулевой размер. Если вы замените -exec
окончание с /;
на +
, find оптимизирует создание подпроцесса, вызывая только rm
минимально возможное количество раз, часто всего один раз.
Вот как я бы изменил эту команду:
find ./ -type f -size 0 -exec rm -i {} +
curly brackets
или braces
: {}
может использоваться по-разному
Расширение скобок
Скобки можно использовать для построения последовательностей:
### prints out the numbers from 0 to 10
echo {0..10}
## prints out the same numbers, but in reverse order
echo {10..0}
## prints every second number, from 10 to 0
echo {10..0..2}
## prints every second letter, from z and working its way backwards to a.
echo {z..a..2}
Также мы можем объединить две или более последовательностей:
## prints out a pair of letters, from aa to zz.
echo {a..z}{a..z}
Добавление префиксов и суффиксов:
### adds '"' as prefix and suffix
echo \"{These,words,are,quoted}\"
# output: "These" "words" "are" "quoted"
# concatenates the files file1, file2, and file3 into combined_file.
cat {file1,file2,file3} > combined_file
# copies "file22.txt" to "file22.backup"
cp file22.{txt,backup}
Примечание:
Внутри скобок не допускается использование пробелов, {...}
если только они нецитируетсяилисбежал.
echo {file1,file2}\ :{\ A," B",' C'}
# output: file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C
Расширение расширенной скобы.
Скобки можно использовать для построения массивов. Массивы в Bash определяются путем помещения элементов в скобки ()
и разделения каждого элемента пробелом, например:
month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
Чтобы получить доступ к элементу массива, используйте его индекс в квадратных скобках []
:
# Array indexes start at [0], so [3] points to the fourth item
$ echo ${month[3]}
## output: Apr
Поэтому мы можем создать массив примерно следующим образом:
## builds an array that contains all the 2-letter combinations of the entire alphabet.
letter_combos=({a..z}{a..z})
## contains all the binary numbers for an 8-bit register, in ascending order,
## from 00000000, 00000001, 00000010, etc., to 11111111.
dec2bin=({0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1})
Последнее особенно интересно, потому что теперь мы можем использовать dec2bin для построения 8-битного десятично-двоичного преобразователя. Допустим, вы хотите узнать, чему равно 25 в двоичном виде. Вы можете сделать это:
$ echo ${dec2bin[25]}
## output: 00011001
Но разве нет лучших способов преобразования десятичных чисел в двоичные?
- Да, есть, но все равно интересно, не правда ли?
Группировка команд
{ ... }
может использоваться для размещения списка команд, которые будут выполнены в текущем контексте оболочки. Нетсуб-оболочкасоздается. Точка с запятой ;
(или новая строка) после списканеобходимый.
Скобки ()
используются для запуска команд всуб-оболочка:
menu_type=bar
echo $menu_type
## output: bar
## new lets called in a sub-shell
(menu_type=superbar; echo $menu_type)
## output: superbar
## back to the context
echo $menu_type
## output: bar
Мы не можем получить доступ к новому superbar
значению menu_type
.
Однако, если мы запустим что-то вроде этого:
{ menu_type=superbar; echo $menu_type; }
## output: superbar
echo $menu_type
## output: superbar
{ ... }
не создает дочернюю оболочку, поэтому мы можем получить доступ к значению menu_type
.
{ ... }
также упоминаются как inline group
, по сути, он создает анонимную функцию (т. е. функцию без имени). Проще говоря, в отличие от «стандартной» функции, переменные внутри остаются { ... }
видимыми для остальной части скрипта.
Также { ... }
может использоваться для группировки вывода из нескольких команд в stdout
или для получения перенаправления в его stdin
. Давайте рассмотрим пример:
#!/bin/bash
# rpm-check.sh
# Queries an rpm file for description, listing, and whether it can be installed.
# Saves output to a file.
SUCCESS=0
E_NOARGS=65
if [ -z "$1" ]; then
echo "Usage: `basename $0` rpm-file"
exit $E_NOARGS
fi
{ # Begin command group.
echo
echo "Archive Description:"
rpm -qpi $1 # Query description.
echo
echo "Archive Listing:"
rpm -qpl $1 # Query listing.
echo
rpm -i --test $1 # Query whether rpm file can be installed.
if [ "$?" -eq $SUCCESS ]
then
echo "$1 can be installed."
else
echo "$1 cannot be installed."
fi
echo # End command group.
} > "$1.test" # Redirects output of everything in block to file.
echo "Results of rpm test in file $1.test"
exit 0
Теперь давайте посмотрим, как выполнить перенаправление ввода-вывода в группе stdin
:
#!/bin/bash
File=/etc/fstab
## reads the first two lines of the file
{
read line1
read line2
} < $File
echo "First line in $File is:"
echo "$line1"
echo
echo "Second line in $File is:"
echo "$line2"
exit 0
Еще один пример сохранения вывода группы команд в файл
## exec commands sequentially and redirects the output of the ls command into the png-list.txt file
echo "I found all these png files:"; find . -iname "*.png"; echo "Within this bunch of files:"; ls > png-list.txt
## exec commands sequentially and redirects the output of the group into the png-list.txt file
{ echo "I found all these png files:"; find . -iname "*.png"; echo "Within this bunch of files:"; ls; } > png-list.txt
В чем разница, Тео?
Ну что ж, юный падаван. Второй создает файл png-list.txt
со всеми выходами, начиная со строки “I found all these png files:“
, и заканчивая ls
выводом команды.
Взлом подоболочки
Bash создает подоболочку для { ... }
команды группы фигурных скобокесли и только еслиэто часть конвейера, например:
$ { A=1; { A=2; sleep 2; } ; echo $A; }
## output: 2
$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
## output: 1
Примечание:
Между скобками и списком команд, заключенным в них, есть пробел. Это потому, что {
и }
являются зарезервированными словами (т. е. командами, встроенными в оболочку). Кроме того, список команд должен заканчиваться точкой с запятой ;
или использовать новые строки для разделения команд.
Расширение параметров
Ладно, вернемся к
month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
echo ${month[3]}
## output: Apr
Здесь скобки {}
не используются как часть конструктора последовательности, а как способ генерации расширения параметров. Расширение параметров подразумевает то, что написано на коробке:
он берет переменную или выражение в фигурных скобках и расширяет ее до того значения, которое она представляет.
Что это значит, Тео?
Ну, это значит, что ${...}
он говорит оболочке развернуть то, что внутри нее. В этом случае, month
это массив, который мы определили ранее, то есть:
month=("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
И элемент 3
в массиве указывает на "Apr"
(т.е. первый индекс в массиве в Bash — это [0]
). Это означает echo ${month[3]}
, что после расширения преобразуется в echo "Apr"
.
Интерпретация переменной как ее значения — один из способов ее расширения, но есть еще несколько, которые мы можем использовать. Мы можем использовать расширение параметра, чтобы манипулировать тем, что вы считываете из переменной (например, отрезая кусок от конца).
Предположим, у вас есть такая переменная:
a="This sentence is too longgg"
## chops off the last two gs
echo ${a%gg}
## output: This sentence is too long
Это может быть полезно для преобразования файлов из одного формата в другой. Например, если у нас есть команда, которая берет изображение JPEG image.jpg
и преобразует его в изображение PNG с именем image.png
:
convert image.jpg image.png
Мы можем переписать это так:
i='image.jpg'
## chops off the extension 'jpg' and adds 'png'
convert $i "${i%jpg}png"
## output: convert image.jpg image.png
Но чем это может быть полезнее, чем просто указание имени файла?
Ну, когда у нас есть каталог, содержащий сотни изображений JPEG, которые нужно преобразовать в PNG, просто выполните в нем следующее:
for i in *.jpg; do convert $i ${i%jpg}png; done
… и все картинки автоматически конвертируются. Добро пожаловать, юный падаван.
Если вам нужно отрезать часть от начала переменной, вместо %
, используйте #
:
$ a="Hello World!"
## chops off the word 'Hello' and adds 'Goodbye'
$ echo Goodbye${a#Hello}
## output: Goodbye World!
Заполнитель для текста
Используется после xargs -i
(т.е., опция замены строк). {}
Двойные фигурные скобки являются заполнителем для выходного текста.
## Execute 'echo ./<file>' for each file in the directory
ls . | xargs -i -t echo ./{} $1
# ^^ ^^
Имя пути
Имя пути — это имя файла, включающее полный путь. Например, /home/<user>/Notes/todo.txt
. Иногда его называют абсолютным путем. Мы будем встречать его {}
в основном в find
конструкциях, содержащих -exec <command> \;
. Однако это не встроенная команда оболочки. Если <command>
содержит {}
, то find заменяет полное имя пути выбранного файла на "{}"
.
# Removes all core dump files from user's home directory.
find ~/ -name 'core*' -exec rm {} \;