12.2. Более сложные команды


Команды для более опытных пользователей

find

-exec COMMAND \;

Для каждого найденого файла, соответствующего заданному шаблону поиска, выполняет команду COMMAND. Командная строка должна завершаться последовательностью символов \; (здесь символ ";" экранирован обратным слэшем, чтобы информировать командную оболочку о том, что символ ";" должен быть передан команде find как обычный символ). Если COMMAND содержит {}, то find подставляет полное имя найденого файла вместо "{}".

  1. bash$ find ~/ -name '*.txt'
  2. /home/bozo/.kde/share/apps/karm/karmdata.txt
  3. /home/bozo/misc/irmeyc.txt
  4. /home/bozo/test-scripts/1.txt
  5.        


  1. find /home/bozo/projects -mtime 1
  2. #  Найти все файлы в каталоге /home/bozo/projects и вложенных подкаталогах,
  3. #+ которые изменялись в течение последних суток.
  4. #
  5. #  mtime = время последнего изменения файла
  6. #  ctime = время последнего изменения атрибутов файла (через 'chmod' или как-то иначе)
  7. #  atime = время последнего обращения к файлу
  8. DIR=/home/bozo/junk_files
  9. find "$DIR" -type f -atime +5 -exec rm {} \;
  10. #  Удалить все файлы в каталоге "/home/bozo/junk_files"
  11. #+ к которым не было обращений в течение последних 5 дней.
  12. #
  13. #  "-type filetype", где
  14. #  f = обычный файл
  15. #  d = каталог, и т.п.
  16. #  (Полный список ключей вы найдете в 'man find'.)


  1. find /etc -exec grep '[0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*' {} \;
  2. # Поиск всех IP-адресов (xxx.xxx.xxx.xxx) в файлах каталога  /etc.
  3. # Однако эта команда выводит не только IP-адреса, как этого избежать?
  4. # Примерно так:
  5. find /etc -type f -exec cat '{}' \; | tr -c '.[:digit:]' '\n' \
  6.  | grep '^[^.][^.]*\.[^.][^.]*\.[^.][^.]*\.[^.][^.]*$'
  7. # [:digit:] — один из символьных классов
  8. # введен в стандарт POSIX 1003.2.
  9. # Спасибо S.C.


Note

Не следует путать опцию -exec команды find с внутренней командой Bash — exec.

Пример 12-3. Badname, удаление файлов в текущем каталоге, имена которых содержат недопустимые символы и пробелы.

  1. #!/bin/bash
  2. # Удаление файлов в текущем каталоге, чьи имена содержат недопустимые символы.
  3. for filename in *
  4. do
  5. badname=`echo "$filename" | sed -n /[\+\{\;\"\\\=\?~\(\)\<\>\&\*\|\$]/p`
  6. # Недопустимые символы в именах файлов:     + { ; " \ = ? ~ ( ) < > & * | $
  7. rm $badname 2>/dev/null    # Сообщения об ошибках "выстреливаются" в никуда.
  8. done
  9. # Теперь "позаботимся" о файлах, чьи имена содержат пробельные символы.
  10. find . -name "* *" -exec rm -f {} \;
  11. # На место "{}", find подставит полное имя файла.
  12. # Символ '\' указывает на то, что ';' интерпретируется как обычный символ, а не как конец команды.
  13. exit 0
  14. #---------------------------------------------------------------------
  15. # Строки, приведенные ниже, не будут выполнены, т.к. выше стоит команда "exit".
  16. # Альтернативный вариант сценария:
  17. find . -name '*[+{;"\\=?~()<>&*|$ ]*' -exec rm -f '{}' \;
  18. exit 0
  19. # (Спасибо S.C.)

Пример 12-4. Удаление файла по его номеру inode

  1. #!/bin/bash
  2. # idelete.sh: Удаление файла по номеру inode.
  3. #  Этот прием используется в тех случаях, когда имя файла начинается с недопустимого символа,
  4. #+ например, ? или -.
  5. ARGCOUNT=1                      # Имя файла должно быть передано в сценарий.
  6. E_WRONGARGS=70
  7. E_FILE_NOT_EXIST=71
  8. E_CHANGED_MIND=72
  9. if [ $# -ne "$ARGCOUNT" ]
  10. then
  11.   echo "Порядок использования: `basename $0` filename"
  12.   exit $E_WRONGARGS
  13. fi
  14. if [ ! -e "$1" ]
  15. then
  16.   echo "Файл \""$1"\" не найден."
  17.   exit $E_FILE_NOT_EXIST
  18. fi
  19. inum=`ls -i | grep "$1" | awk '{print $1}'`
  20. # inum = номер inode (index node) файла
  21. # Каждый файл имеет свой inode, где хранится информация о физическом расположении файла.
  22. echo; echo -n "Вы совершенно уверены в том, что желаете удалить \"$1\" (y/n)? "
  23. # Ключ '-v' в команде 'rm' тоже заставит команду вывести подобный запрос.
  24. read answer
  25. case "$answer" in
  26. [nN]) echo "Передумали?"
  27.       exit $E_CHANGED_MIND
  28.       ;;
  29. *)    echo "Удаление файла \"$1\".";;
  30. esac
  31. find . -inum $inum -exec rm {} \;
  32. echo "Файл "\"$1"\" удален!"
  33. exit 0

Дополнительные примеры по использованию команды find вы найдете в Пример 12-25, Пример 3-4 и Пример 10-9. В страницах справочного ркуоводства (man find) вы найдете более подробную информацию об этой достаточно сложной и мощной команде.

xargs

Команда передачи аргументов указанной команде. Она разбивает поток аргументов на отдельные составляющие и поочередно передает их заданной команде для обработки. Эта команда может рассматриваться как мощная замена обратным одиничным кавычкам. Зачастую, когда команды, заключенные в обратные одиночные кавычки, завершаются с ошибкой too many arguments (слишком много аргументов), использование xargs позволяет обойти это ограничение. Обычно, xargs считывает список аргументов со стандартного устройства ввода stdin или из канала (конвейера), но может считывать информацию и из файла.

Если команда не задана, то по-умолчанию выполняется echo. При передаче аргументов по конвейеру, xargs допускает наличие пробельных символов и символов перевода строки, которые затем автоматически отбрасываются.

  1. bash$ ls -l
  2. total 0
  3. -rw-rw-r--    1 bozo  bozo         0 Jan 29 23:58 file1
  4. -rw-rw-r--    1 bozo  bozo         0 Jan 29 23:58 file2
  5. bash$ ls -l | xargs
  6. total 0 -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file1 -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file2
  7.        


ls | xargs -p -l gzip — упакует с помощью gzip все файлы в текущем каталоге, выводя запрос на подтверждение для каждого файла.

Tip

xargs имеет очень любопытный ключ -n NN, который ограничивает количество передаваемых аргументов за один "присест" числом NN.

ls | xargs -n 8 echo — выведет список файлов текущего каталога в 8 колонок.

Tip

Еще одна полезная опция — -0, в комбинации с find -print0 или grep -lZ позволяет обрабатывать аргументы, содержащие пробелы и кавычки.

find / -type f -print0 | xargs -0 grep -liwZ GUI | xargs -0 rm -f

grep -rliwZ GUI / | xargs -0 rm -f

Обе вышеприведенные команды удалят все файлы, содержащие в своем имени комбинацию символов "GUI". (Спасибо S.C.)

Пример 12-5. Использование команды xargs для мониторинга системного журнала

  1. #!/bin/bash
  2. # Создание временного файла мониторинга в текщем каталоге,
  3. # куда переписываются несколько последних строк из /var/log/messages.
  4. # Обратите внимание: если сценарий запускается обычным пользователем,
  5. # то файл /var/log/messages должен быть доступен на чтение этому пользователю.
  6. #         #root chmod 644 /var/log/messages
  7. LINES=5
  8. ( date; uname -a ) >>logfile
  9. # Время и информация о системе
  10. echo --------------------------------------------------------------------- >>logfile
  11. tail -$LINES /var/log/messages | xargs |  fmt -s >>logfile
  12. echo >>logfile
  13. echo >>logfile
  14. exit 0
  15. # Упражнение:
  16. # --------
  17. #  Измените сценарий таким образом, чтобы он мог отслеживать изменения в /var/log/messages
  18. #+ с интервалом в 20 минут.
  19. #  Подсказка: воспользуйтесь командой "watch".

Пример 12-6. copydir, копирование файлов из текущего каталога в другое место.

  1. #!/bin/bash
  2. # Копирует все файлы из текущего каталога
  3. # в каталог, указанный в командной строке.
  4. if [ -z "$1" ]   # Выход, если каталог назначения не задан.
  5. then
  6.   echo "Порядок использования: `basename $0` directory-to-copy-to"
  7.   exit 65
  8. fi
  9. ls . | xargs -i -t cp ./{} $1
  10. # Этот сценария является точным эквивалентом
  11. #    cp * $1
  12. # если в именах файлов не содержатся пробельные символы.
  13. exit 0

Пример 12-7. Завершение работы процесса по его имени

  1. #!/bin/bash
  2. # kill-byname.sh: Завершение работы процесса по его имени.
  3. # Сравните этот сценарий с kill-process.sh.
  4. #  Пример,
  5. #+ Попробуйте запустить команду "./kill-byname.sh xterm" --
  6. #+ и понаблюдайте как закроются все окна xterm.
  7. #  Внимание:
  8. #  --------
  9. #  Этот сценарий может представлять определенную угрозу.
  10. #  Запуск этого сценария (особенно с правами root)
  11. #+ может привести к потере несохраненных данных и другим неожиданным эффектам.
  12. E_BADARGS=66
  13. if test -z "$1"  # Проверка — задано ли имя процесса
  14. then
  15.   echo "Порядок использования: `basename $0` останавливаемый(ые)_процесс(ы)"
  16.   exit $E_BADARGS
  17. fi
  18. # ---------------------------------------------------------
  19. # Примечание:
  20. # Ключ -i команды xargs — это "замена строки" .
  21. # Фигурные скобки — это замещаемая строка.
  22. # 2&>/dev/null — подавляет вывод сообщений об ошибках.
  23. # ---------------------------------------------------------
  24. PROCESS_NAME="$1"
  25. ps ax | grep "$PROCESS_NAME" | awk '{print $1}' | xargs -i kill {} 2&>/dev/null
  26. exit $?

Пример 12-8. Подсчет частоты встречаемости слов using xargs

  1. #!/bin/bash
  2. # wf2.sh: Грубый подсчет частоты встречаемости слова в текстовом файле.
  3. # Команда 'xargs' используется для выделения отдельных слов из строки.
  4. # Сравните этот сценарий с "wf.sh".
  5. # Проверка — задано ли имя файла из командной строки.
  6. ARGS=1
  7. E_BADARGS=65
  8. E_NOFILE=66
  9. if [ $# -ne "$ARGS" ]
  10. # Передано корректное число аргументов?
  11. then
  12.   echo "Порядок использования: `basename $0` имя_файла"
  13.   exit $E_BADARGS
  14. fi
  15. if [ ! -f "$1" ]       # Проверка наличия файла.
  16. then
  17.   echo "Файл \"$1\" не найден."
  18.   exit $E_NOFILE
  19. fi
  20. #######################################################
  21. cat "$1" | xargs -n1 | \
  22. #  Вывод содержимого файла, по одному слову в строке.
  23. tr A-Z a-z | \
  24. # Преобразование в нижний регистр.
  25. sed -e 's/\.//g'  -e 's/\,//g' -e 's/ /\
  26. /g' | \
  27. #  Отбросить точки и запятые и
  28. #+ заменить пробелы символами перевода строки,
  29.   sort | uniq -c | sort -nr
  30. # В заключение — подсчитать и отсортировать по частоте встречаемости.
  31. #######################################################
  32. #  Этот сценарий выполняет те же действия, что и "wf.sh",
  33. #+ но он более "тяжелый" и работает значительно медленнее.
  34. exit 0
expr

Универсальный обработчик выражений: вычисляет заданное выражение (аргументы должны отделяться пробелами). Выражения могут быть арифметическими, логическими или строковыми.

expr 3 + 5

возвратит 8

expr 5 % 3

возвратит 2

expr 5 \* 3

возвратит 15

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

y=`expr $y + 1`

Операция инкремента переменной, то же самое, что и let y=y+1, или y=$(($y+1)). Пример подстановки арифметических выражений.

z=`expr substr $string $position $length`

Извлекает подстроку длиной $length символов, начиная с позиции $position.

Пример 12-9. Пример работы с expr

  1. #!/bin/bash
  2. # Демонстрация некоторых приемов работы с командой 'expr'
  3. # =======================================
  4. echo
  5. # Арифметические операции
  6. # -------------- --------
  7. echo "Арифметические операции"
  8. echo
  9. a=`expr 5 + 3`
  10. echo "5 + 3 = $a"
  11. a=`expr $a + 1`
  12. echo
  13. echo "a + 1 = $a"
  14. echo "(инкремент переменной)"
  15. a=`expr 5 % 3`
  16. # остаток от деления (деление по модулю)
  17. echo
  18. echo "5 mod 3 = $a"
  19. echo
  20. echo
  21. # Логические операции
  22. # ---------- --------
  23. #  Возвращает 1 если выражение истинноо, 0 — если ложно,
  24. #+ в противоположность соглашениям, принятым в Bash.
  25. echo "Логические операции"
  26. echo
  27. x=24
  28. y=25
  29. b=`expr $x = $y`         # Сравнение.
  30. echo "b = $b"            # 0  ( $x -ne $y )
  31. echo
  32. a=3
  33. b=`expr $a \> 10`
  34. echo 'b=`expr $a \> 10`, поэтому...'
  35. echo "Если a > 10, то b = 0 (ложь)"
  36. echo "b = $b"            # 0  ( 3 ! -gt 10 )
  37. echo
  38. b=`expr $a \< 10`
  39. echo "Если a < 10, то b = 1 (истина)"
  40. echo "b = $b"            # 1  ( 3 -lt 10 )
  41. echo
  42. # Обратите внимание на необходимость экранирования операторов.
  43. b=`expr $a \<= 3`
  44. echo "Если a <= 3, то b = 1 (истина)"
  45. echo "b = $b"            # 1  ( 3 -le 3 )
  46. # Существует еще оператор "\>=" (больше или равно).
  47. echo
  48. echo
  49. # Операции сравнения
  50. # -------- ---------
  51. echo "Операции сравнения"
  52. echo
  53. a=zipper
  54. echo "a is $a"
  55. if [ `expr $a = snap` ]
  56. then
  57.    echo "a — это не zipper"
  58. fi
  59. echo
  60. echo
  61. # Операции со строками
  62. # -------- — --------
  63. echo "Операции со строками"
  64. echo
  65. a=1234zipper43231
  66. echo "Строка над которой производятся операции: \"$a\"."
  67. # length: длина строки
  68. b=`expr length $a`
  69. echo "длина строки \"$a\" равна $b."
  70. # index: позиция первого символа подстроки в строке
  71. b=`expr index $a 23`
  72. echo "Позиция первого символа \"2\" в строке \"$a\" : \"$b\"."
  73. # substr: извлечение подстроки, начиная с заданной позиции, указанной длины
  74. b=`expr substr $a 2 6`
  75. echo "Подстрока в строке \"$a\", начиная с позиции 2,\
  76. и длиной в 6 символов: \"$b\"."
  77. #  При выполнении поиска по шаблону, по-умолчанию поиск
  78. #+ начинается с ***начала*** строки.
  79. #
  80. #        Использование регулярных выражений
  81. b=`expr match "$a" '[0-9]*'`               #  Подсчет количества цифр.
  82. echo Количество цифр с начала строки \"$a\" : $b.
  83. b=`expr match "$a" '\([0-9]*\)'`           #  Обратите внимание на экранирование круглых скобок
  84. #                   ==      ==
  85. echo "Цифры, стоящие в начале строки \"$a\" : \"$b\"."
  86. echo
  87. exit 0
Important

Вместо оператора match можно использовать оператор :. Например, команда b=`expr $a : [0-9]*` является точным эквивалентом для b=`expr match $a [0-9]*` в примере, рассмотренном выше.

  1. #!/bin/bash
  2. echo
  3. echo "Операции над строками с использованием конструкции \"expr \$string : \" "
  4. echo "========================================================================"
  5. echo
  6. a=1234zipper5FLIPPER43231
  7. echo "Строка, над которой выполняются операции: \"`expr "$a" : '\(.*\)'`\"."
  8. #     Экранирование круглых скобок в шаблоне                    ==  ==
  9. #  Если скобки не экранировать...
  10. #+ то 'expr' преобразует строковый операнд в целое число.
  11. echo "Длина строки \"$a\" равна `expr "$a" : '.*'`."   # Длина строки
  12. echo "Количество цифр с начала строки \"$a\" равно `expr "$a" : '[0-9]*'`."
  13. # ------------------------------------------------------------------------- #
  14. echo
  15. echo "Цифры, стоящие в начале строки \"$a\" : `expr "$a" : '\([0-9]*\)'`."
  16. #                                                             ==      ==
  17. echo "Первые 7 символов в строке \"$a\" : `expr "$a" : '\(.......\)'`."
  18. #     ======                                          ==       ==
  19. # Опять же, необходимо экранировать круглые скобки в шаблоне.
  20. #
  21. echo "Последние 7 символов в строке \"$a\" : `expr "$a" : '.*\(.......\)'`."
  22. #     =========                  оператор конца строки     ^^
  23. #  (фактически означает переход через любое количество символов, пока
  24. #+  не будет найдена требуемая подстрока)
  25. echo
  26. exit 0


Этот пример демонстрирует необходимость экранирования оператора группировки — \( ... \) в регулярных выражениях, при поиске по шаблону командой expr.

Perl, sed и awk имеют в своем распоряжении более мощный аппарат анализа строк. Коротенький скрипт на sed или awk, внутри сценария (см. Section 33.2) — значительно более привлекательная альтернатива использованию expr при анализе строк.

Дополнительные примеры, по обработке строк, вы найдете в Section 9.2.