====== Bash ====== [[https://leanpub.com/programming-from-scratch/read|Илья Шпигорь - Программирование на Bash с нуля]]\\ [[https://devhints.io/bash|Bash scripting cheatsheet]]\\ [[https://learnxinyminutes.com/docs/bash/|Learn X in Y minutes: Bash]]\\ [[https://mywiki.wooledge.org/BashGuide|BashGuide]]\\ [[https://tldp.org/LDP/abs/html|Advanced Bash-Scripting Guide]]\\ Плагины для VSCode: Remote - SSH (подключиться по ssh на удаленный хост), Bash Run Button (кнопка запуска скрипта из GUI). ===== Основное ===== ==== Комментарии ==== # Shebang (в начале скрипта) #!/bin/bash # Комментарий : ' Комментарий в несколько строк ' ==== Переменные ==== # Переменные declare -r pwdfile=/etc/passwd # переменная read-only pwdfile=/etc/abc.txt # переопределение не сработает, т. к. read-only declare -l str="STRing" # string (-l - lowercase) declare -u str="STRing" # STRING (-u - uppercase) # Quoting a variable preserves whitespace hello="A B C D" echo $hello # A B C D echo "$hello" # A B C D # Переменные окружения (должна быть в ВЕРХНЕМ_РЕГИСТРЕ) set # вывести все переменные export FLOWER="Tulip" # задать переменную окружения, работает до выхода из системы unset FLOWER # убрать переменную # В рамках скрипта все переменные по умолчанию глобальные - они будут действовать и в функциях, и в местах их вызова. # Подставить значение (здесь: default), если его нет в переменной echo "my variable contains ${MY_VAR:-default}" ==== Алиасы ==== # Алиас alias showlog="tail -f /var/log/messages" # задать алиас, работает до выхода из системы alias showlog # посмотреть команду алиаса showlog # Чтобы алиас или переменная окружения работали всегда для конкретного пользователя, нужно добавить их в ~/.bashrc # Глобальные настройки для всех пользователей - /etc/bashrc (для функций и алиасов) или /etc/profile.d/custom.sh для переменных окружения ==== Математические операции ==== ''%%(( мат. операция ))%%'' - чтобы провести вычисления, нужно выражение заключить в двойные скобки.\\ Чтобы выражение занести в переменную, перед двойными скобками нужен знак ''$''. |Степень |''%%$a ** $b%%'' | |Умножение |''$a * $b'' | |Деление |''$a / $b'' | |Деление с остатком (modulo) |''$a % $b'' | |Сложение |''$a + $b'' | |Вычитание |''$a - $b'' | a=4 b=$((a+2)) # 6 ((b++)) # 7 ((b--)) # 6 ((b*=5)) # 65 ((b+=5)) # 70 ((b/=10)) # 7 ((b-=2)) # 5 echo $((10/3)) # 3, т. к. bash работает только с целыми числами echo 10/3 |bc -l # для работы с дробями нужно привлекать bc ==== Сравнение ==== ''%%[[ expression ]]%%'' - в двойных квадратных скобках, выражение должно отделяться от скобок пробелом, возвращает true (1) или false (0). ^ ^Для сравнения строк ^Для сравнения чисел ^ |Меньше |''$a < $b'' |''$a -lt $b'' | |Больше |''$a > $b'' |''$a -gt $b'' | |Меньше или равно|''$a %%<=%% $b'' |''$a -le $b'' | |Больше или равно |''$a %%>=%% $b'' |''$a -ge $b'' | |Равно |''$a == $b'' |''$a -eq $b'' | |Не равно |''$a != $b'' |''$a -ne $b'' | ^ Логические операторы ^^ | И |''%%[[ $a && $b ]]%%'' | | ИЛИ |''%%[[ $a || $b ]]%%'' | | НЕ |''%%[[ ! $a ]]%%'' | | Ноль? |''%%[[ -z $a ]]%%'' | | Не ноль? |''%%[[ -n $a ]]%%'' | ==== Строки ==== a="hello" b="world" c="$a $b" # Объединение строк echo $c hello world echo ${#c} # длина строки 11 echo ${c:3} # начать вывод с 4-го символа (нумерация начинается с 0) lo world echo ${c:3:5} # 5 символов после начала вывода lo wo echo ${c: -4} # вывод с конца orld echo ${c: -4:3} # 3 из 4-х последних orl fruits="apple banana banana banana banana pear" echo ${fruits/banana/cherry} # замена 1-го вхождения apple cherry banana banana banana pear echo ${fruits//banana/cherry} # замена всех вхождений apple cherry cherry cherry cherry pear echo ${fruits/#apple/cherry} # замена только в самом начале строки (при несовпадении замены не будет) cherry banana banana banana banana pear echo ${fruits/%pear/cherry} # замена только в самом конце строки (при несовпадении замены не будет) apple banana banana banana banana cherry echo ${fruits/ban*/cherry} # шаблоны сравнения тоже работают apple cherry https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion ==== Heredoc ==== Heredoc - when you need to pass a multiline block of text or code to an interactive command, such as tee, cat or sftp\\ https://linuxize.com/post/bash-heredoc cat << EOF Hello Good bye EOF # <<- убирает знаки табуляции из начала строк ftp -n <<- EOF open mirrors.xmission.com user anonymous nothinghere ascii cd gutenberg get GUTINDEX.00 bye EOF ==== Цвета ==== === echo -e === Есть стандартный довольно замысловатый синтаксис.\\ Используется ''echo -e'', для начала обозначения цвета ''\e['', дальше идёт ''начертание;цвет текста;цвет фона'' и в конце ''m''.\\ Для того, чтобы сбросить настройки в конце текста, пишется ''\e[0m''. echo -e "\e[5;97;41mERROR\e[0m" ^Color ^Foreground Code ^Background Code ^ |Black |30 |40 | |Red |31 |41 | |Green |32 |42 | |Yellow |33 |43 | |Blue |34 |44 | |Magenta |35 |45 | |Cyan |36 |46 | |Light Gray |37 |47 | |Gray |90 |100 | |Light Red |91 |101 | |Light Green |92 |102 | |Light Yellow |93 |103 | |Light Blue |94 |104 | |Light Magenta |95 |105 | |Light Cyan |96 |106 | |White |97 |107 | ^Code ^Description ^ |0 |Reset/Normal | |1 |Bold text | |2 |Faint text (бледный) | |3 |Italics | |4 |Underlined text | === tput === Есть вариант использования tput. echo -e $(tput setaf 7; tput setab 1; tput blink)"ERROR"$(tput sgr0) # Все варианты for fg_color in {0..7}; do set_foreground=$(tput setaf $fg_color) for bg_color in {0..7}; do set_background=$(tput setab $bg_color) echo -n $set_background$set_foreground printf ' F:%s B:%s ' $fg_color $bg_color done echo $(tput sgr0) done {{:learning:pasted:20220529-082145.png}} ^Value ^Color ^ |0 |Black | |1 |Red | |2 |Green | |3 |Yellow | |4 |Blue | |5 |Magenta | |6 |Cyan | |7 |White | |8 |Not used | |9 |Reset to default color | https://linuxcommand.org/lc3_adv_tput.php ==== Классы символов ==== ^POSIX class ^Equivalent to ^Matches ^ |''[:alnum:]'' |''[A-Za-z0-9]'' |digits, uppercase and lowercase letters | |''[:alpha:]'' |''[A-Za-z]'' |upper- and lowercase letters | |''[:ascii:]'' |''[\x00-\x7F]'' |ASCII characters | |''[:blank:]'' |''[ \t]'' |space and TAB characters only | |''[:cntrl:]'' |''[\x00-\x1F\x7F]'' |Control characters | |''[:digit:]'' |''[0-9]'' |digits | |''[:graph:]'' |''[^ [:cntrl:]]'' |graphic characters (all characters which have graphic representation) | |''[:lower:]'' |''[a-z]'' |lowercase letters | |''[:print:]'' |''%%[[:graph:] ]%%'' |graphic characters and space | |''[:punct:]'' |''%%[-!"#$%&'()*+,./:;<=>?@[]^_`{|}~]%%'' |all punctuation characters (all graphic characters except letters and digits) | |''[:space:]'' |''[ \t\n\r\f\v]'' |all blank (whitespace) characters, including spaces, tabs, new lines, carriage returns, form feeds, and vertical tabs | |''[:upper:]'' |''[A-Z]'' |uppercase letters | |''[:word:]'' |''[A-Za-z0-9_]'' |word characters | |''[:xdigit:]'' |''[0-9A-Fa-f]'' |hexadecimal digits | Примеры * ''%%a[[:digit:]]b%%'' matches a0b, a1b, ..., a9b. * ''%%a[:digit:]b%%'' is invalid, character classes must be enclosed in brackets * ''%%[[:digit:]abc]%%'' matches any digit, as well as a, b, and c. * ''%%[abc[:digit:]]%%'' is the same as the previous, matching any digit, as well as a, b, and c * ''%%[^ABZ[:lower:]]%%'' matches any character except lowercase letters, A, B, and Z. https://github.com/micromatch/posix-character-classes ==== Прочее ==== === Посылка данных в другой скрипт === # first.sh MESSAGE="Hello there" export MESSAGE ./second.sh # second.sh echo "The message is: $MESSAGE" # Теперь при вызове ./first.sh будет показываться сообщение The message is: Hello there === Изменение строки запроса консоли === Для этого можно менять переменные окружения PS set |egrep ^PS PS1='\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' PS2='> ' PS4='+ ' export PS1='[ \u @ \w ]> ' # Есть разные параметры - \d (date), \t (time), \j (jobs), \h (hostname), \w (current dir) и т. д. # История (файл - ~/.bash_history) history -cw # очистить и записать файл # Скрипт - в начале нужно вставлять строку (# необходим в начале строки): #!/bin/bash which cp # выводит полный путь к команде (программе), в данном случае cp whereis cp # выводит пути к бинарнику команды/программы, к исходникам и к мануалам locate kernel # быстрый поиск путей, содержащих слово kernel. Gрограмма locate может быть не установлена (apt install plocate). updatedb # обновление базы locate, чтобы она увидела только что созданные файлы (она обновляется по крону) ===== Сочетания клавиш в консоли ===== * Ctrl+a - в начало строки * Ctrl+e - в конец строки * Ctrl+k - удалить всё от курсора до конца строки * Ctrl+u - удалить всё от курсора до начала строки * Ctrl+l - очистить экран, кроме текущей строки * Ctrl+w - удалить последнее слово (до разделителя-пробела) * Ctrl+t - тащит символ, находящийся позади курсора, вперёд по строке * Ctrl+f - forward, перемещать курсор вперёд по строке (то же, что и ->) * Ctrl+b - back, перемещать курсор назад по строке (то же, что и <-) * Ctrl+h - удалить предыдущий символ (как backspace) * Ctrl+d - удалить следующий символ (как delete) * Ctrl+p - показать предыдущую команду (то же, что и ↑) ===== Потоки (stdin, stdout, stderr) и перенаправления ===== stdout - стандартный вывод. cat * > file1.txt # Перенаправить stdout (>) в файл echo "text" >> file1.txt # Перенаправить, добавляя (>>) в файл stderr - вывод ошибок. ls absentDir > file1.txt # если каталог не существует, file1.txt будет пустым, т. к. ошибка не будет в стандартном выводе ls absentDir 2> file1.txt # Перенаправление потока ошибок (2>) ls absentDir 2>> file1.txt # Перенаправление потока ошибок (2>>), добавляя в файл ls absentDir 2>> /dev/null # Подавление вывода ошибок # Комбинированный вывод - содержимое существующих файлов будет в out.txt, а ошибки - в err.txt cat file1 file2 nofile > out.txt 2> err.txt # И содержимое, и ошибки будут валиться в out.txt cat file1 file2 nofile > out.txt 2>&1 Как сделать, чтобы при перенаправлении потока в файл он не затирался set -o noclobber # включить опцию в bash echo "line10" > file1 # -bash: file1: cannot overwrite existing file echo "line10" >> file1 # добавление будет работать set +o noclobber # выключить ===== Именованный канал (named pipe) ===== Помимо обычных каналов, которые существуют только пока выполняется команда, есть и именованные, работающие пока они не будут удалены либо до перезагрузки компа. Именованные каналы работают по принципу FIFO (первый вошёл - первый вышел). Сиздать именованный канал можно командами ''mkfifo'' или ''mknod p''. В Linux именованный канал отображается как файл с типом p и размером 0. Создав такой канал, мы также можем перенаправить в него вывод других команд, но в отличие от неименованного канала, перенаправление вывода не завершится и надо будет выполнить прерывание Ctrl+c: mknod metal_pipe p cat file1 > metal_pipe # ctrl+c ls -l metal_pipe prw-rw----- 1 root root 0 Sep 23 12:51 metal_pipe ===== Условие ===== c=1 if [ $c -eq 1 ] then echo "c equals 1" elif [ $c -lt 1 ] then echo "c is less than 1" else echo "c is bigger than 1" fi # Вместо -eq, -gt и -lt и т. д. можно писать ==, >, <, >= и т. д., но тогда нужны двойные круглые скобки: if (( $c <= 1 )) # Оператор "AND". Все строки ниже дают один и тот же эффект. if [ $c -gt 10 ] && [ $c -lt 30 ] if [[ $c -gt 10 && $c -lt 30 ]] if [ $c -gt 10 -a $c -lt 30 ] # Оператор "OR". Все строки ниже дают один и тот же эффект. if [ $c -gt 10 ] || [ $c -lt 30 ] if [[ $c -gt 10 || $c -lt 30 ]] if [ $c -gt 10 -o $c -lt 30 ] # match if [[ $a =~ [0-9]+ ]] # есть ли в строке цифры # case - типа switch в Powershell (https://linuxize.com/post/bash-case-statement/) echo -n "Enter the name of a country: " read COUNTRY echo -n "The official language of $COUNTRY is " case $COUNTRY in # скобка завершает список паттернов Lithuania) echo "Lithuanian";; # ;; - завершение условия Romania | Moldova) echo "Romanian";; Italy | "San Marino" | Switzerland | "Vatican City") echo "Italian";; *) echo "unknown";; # значение по умолчанию esac # завершение case # Выбор из пунктов VEGS=('TOMATO' 'CUCUMBER' 'ONION' 'PEASE' 'RADISH' 'DILL') select VEG in ${VEGS[@]} do case $VEG in TOMATO | CUCUMBER | ONION | PEASE | RADISH | DILL) echo "You've selected $VEG";; *) echo "Select from 1 to ${#VEGS[@]}";; esac done ===== Циклы ===== # while number=1 while [ $number -le 10 ] do echo "$number" ((number++)) # можно ((number+=1)) done # until number=1 until [ $number -gt 10 ] do echo "$number" ((number++)) done # for for i in 1 2 3 4 5 # {1..5} или ${array[@]} do echo $i done # for i in {0..100..5} - c 0 до 100 с шагом 5 # for c in {01..75}; do echo $c; done; # for two digits # for c in {001..075}; do echo $c; done; # for three digits for (( i=0; i<5; i++ )) # (( задать переменную; условие продолжения цикла; действие )) do echo $i done # for loop для ассоциированного массива declare -A array array["name"]="Vasya" array["title"]="manager" for i in "${!array[@]}" do echo "$i: ${array[$i]}" done # прерывание цикла for (( i=0; i<=10; i++ )) do if [ $i -gt 5 ] then break # прервать цикл fi echo $i done # продолжение цикла for (( i=0; i<=10; i++ )) do if [[ $i -eq 3 || $i -eq 7 ]] then continue # перейти сразу на след. итерацию fi echo $i done ===== Входные данные ===== #! /bin/bash echo $0 $1 $2 $3 # $0 - имя скрипта bash test.sh letterA letterB letterC test.sh letterA letterB letterC # Вариант входных данных в скрипт через массив args=("$@") #you can also specify the array size here echo ${args[0]} ${args[1]} ${args[2]} args=("$@") echo $@ # вывести все элементы массива echo $# # вывести число элементов в массиве for i in $@; do echo $i done echo "There are $# arguments." ./script.sh one two three one two three There are 3 arguments. # Ввод данных пользователем при выполнении скрипта echo "What's your name?" read name echo "Enter your password" read -s pass # выводиться на экран во время ввода не будет read -p "What's your favourite colour? " colour # не переводить строку при вводе echo "Name: $name, Password: $pass, Colour: $colour" # Выбор из пунктов select animal in "cat" "dog" "bird" "fish" do echo "You selected $animal!" break done 1) cat 2) dog 3) bird 4) fish #? 4 You selected fish! # С выходом select option in "cat" "dog" "quit" do case $option in cat) echo "Cats meow";; dog) echo "Dogs bark";; quit) break;; *) echo "You've chosen something that I don't know";; esac done ===== Проверки ===== # Программа работает, только если аргументов достаточно if [ $# -lt 3 ]; then cat <<- EOF Three arguments required: name, id, favourite color. EOF else echo "Name: $1" echo "ID: $2" echo "Color: $3" fi # Повторение вопроса, если нет ответа read -p "Color? " color while [[ -z "$color" ]]; do read -p "Give me an answer! Color? " color done echo "$color was selected." # Ответ по умолчанию, если нет ответа read -p "Color? [green]" color while [[ -z "$color" ]]; do color="green" done echo "$color was selected." # Принимать ответ только если это 4 цифры, если год после 1980 и он не начинается с нуля read -p "Year? [4 digits] " y while [[ ! $y =~ [0-9]{4} ]] || (( $y < 1980 )) || [[ $y =~ ^0 ]]; do read -p "Wrong format or earlier than 1980! Year? " y done echo "Selected year: $y" # Игра "Угадай число" #!/bin/bash function game { s=$((1+ $RANDOM % 10)) read -p "Угадайте число от 1 до 10: " n while [[ $n != $s ]]; do if [[ ! $n ]]; then read -p "Вы ничего не ввели, напишите число от 1 до 10: " n elif [[ ! $n =~ ^[0-9]0?$ ]]; then read -p "Вы ошиблись в написании числа, попробуйте ещё раз: " n elif [[ $n -lt 1 || $n -gt 10 ]]; then read -p "Вы ввели число вне диапазона, попробуйте ещё раз: " n elif [[ $s -gt $n ]]; then read -p "Загаданное число больше, попытайтесь ещё: " n elif [[ $s -lt $n ]]; then read -p "Загаданное число меньше, попытайтесь ещё: " n fi done echo "Поздравляю, вы угадали, это было число $s!" } if [[ $1 =~ [Gg][Aa][Mm][Ee] ]]; then game else echo "Чтобы начать игру, запустите этот скрипт с аргументом 'game'" fi Угадайте число от 1 до 10: ыыы Вы ошиблись в написании числа, попробуйте ещё раз: Вы ничего не ввели, напишите число от 1 до 10: 0 Вы ввели число вне диапазона, попробуйте ещё раз: 14 Вы ввели число вне диапазона, попробуйте ещё раз: 1 Загаданное число больше, попытайтесь ещё: 8 Загаданное число меньше, попытайтесь ещё: 6 Загаданное число меньше, попытайтесь ещё: 4 Загаданное число меньше, попытайтесь ещё: 2 Поздравляю, вы угадали, это было число 2! ===== Вывод информации ===== # 1 - standard output, 2 - standard error ls 1> output.txt 2> errors.txt ls 2>&1 # перенаправить ошибки в стандартный вывод ===== Строки ===== if [ "$st1" == "$st2" ] # match/not match if [ "$st1" \ "$st2" ] # st2 smaller/equal c=$st1$st2 # concatenation echo ${st1,,} # to lowercase echo ${st2^^} # to uppercase echo ${st1^} # capitalize first letter ===== Числа ===== n1=256 n2=7 echo $(( n1 % n2 )) # деление с остатком (так же для +, -, *, /) echo $(expr $n1 + $n2) # вариант через expr (знак умножения надо экранировать - $n1 \* $n2) echo "obase=10; ibase=16; 2042A59C3D70B" | bc # конвертер hex в dec # Арифметическая оценка ((var = 12 + 7)) # код возврата 0 # Арифметическая оценка var=$((12 + 7)) # var=19 # Префиксный и постфиксный инкремент var=1 ((result = ++var)) # result и var равны 2 ((result = var++)) # result равен 1, var равен 2 # Тернарный оператор # Замена конструкции if-then-else if ((var < 10)) then ((result = 0)) else ((result = var)) fi # на (( УСЛОВИЕ ? ДЕЙСТВИЕ 1 : ДЕЙСТВИЕ 2 )) ((result = var < 10 ? 0 : var)) # в bash тернарный оператор можно использовать только в арифм. оценке или подстановке, команды использовать нельзя ===== Массивы ===== food=('bread' 'butter' 'cheese' 'juice') echo "${food[@]}" # вывести всё bread butter cheese juice echo "${food[2]}" # вывести элемент с индексом 2 cheese echo "${food[2+1]}" # вывести элемент с индексом 3 juice echo "${food[@]: -1}" # вывести последний элемент juice echo "${food[@]:1:2}" # вывести 2 элемента, начиная с 1 butter cheese food[5]="orange" # Добавить элемент в 5-й индекс. 4-й здесь будет пустой food+=("kiwi") # Добавить элемент в конец массива echo "${!food[@]}" # вывести индексы 0 1 2 3 5 6 echo "${#food[@]}" # вывести кол-во элементов 6 files=$(ls Documents/*.txt) # Так делать не надо - будет строка declare -a files=(Documents/*.txt) # правильный вариант (индексируемый массив) files=(Documents/*.txt) # Можно и так # Определение элементов массива по отдельности doc="/home/user/doc.txt" files=([0]="$doc" [5]="/usr/share/doc/xz/README") # пропуск элементов (sparse array) # Ассоциированные массивы # Можно задать сразу все элементы declare -A user=(["Name"]="Ivan" ["Surname"]="Petrov" ["Phone"]="+7 000 000-00-00" ["Mail"]="i.petrov@example.com") # Можно задать элементы постепенно declare -A myarray myarray[size]=big myarray[room]="meeting room 1" echo The "${myarray[room]}" is "${myarray[size]}" # Содержимое массива declare -p myarray declare -A myarray=([room]="meeting room 1" [size]="big" ) # Список ключей - ! echo "${!myarray[@]}" room size # Удалить массив или элемент unset myarray unset myarray[0] unset myarray[size] # Текст в массив mapfile -t names_array < names.txt # синоним команды mapfile - readarray ===== Функции ===== Рекомендации: - Имя функции должно сообщать читателю кода, что она делает. - В функциях нужно использовать только локальные переменные. В ином случае конфликт имён локальных и глобальных переменных решается соглашением об их именовании. - Не надо использовать глобальные переменные в функциях. Вместо этого значение глобальной переменной передаётся в функцию через параметр. - Не надо использовать ключевое слово ''function'' при объявлении функций. Оно есть в Bash, но отсутствует в POSIX-стандарте. Использование ''function'' решает проблему между именем функции и псевдонимом, если их имена совпадают. В такой ситуации функцию можно вызвать, поставив перед её именем обратный слэш: ''\name'' funcName() { echo $1 $2 # входные позиционные аргументы } funcName "Good morning," "man!" Good morning, man! # Объявление функции в одну строку cpuinfo() { cat /proc/cpuinfo ; } # ; перед } в конце обязательно # Удалить функцию, но не одноимённую переменную (-f) unset -f cpuinfo # Все аргументы, передаваемые в функцию - $@ count() { c=1 for arg in $@; do printf "$c:\t$arg\n" ((c++)) done } count $(ls) # Параметры (флаги). # Двоеточие после параметра значит, что нужно указывать значение. # Если двоеточия нет, то проверяется только наличие параметра. while getopts u:p:ab option; do case $option in u) user=$OPTARG;; p) pass=$OPTARG;; a) echo "Got the A parameter";; b) echo "Got the B parameter";; ?) echo "Unknown parameter $OPTARG!";; esac done echo "User: $user / Pass: $pass" # Область переменных: локальные переменные внутри функций задаются только в пределах этих функций с помощью команды local, # иначе можно, при наличии одинаковых имён в скрипте и внутри функции, получить проблемы, т. к. по умолчанию все переменные - глобальные. suncTest() { i="function variable" } i="script variable" echo $i # script variable suncTest echo $i # function variable suncTest() { local i="function variable" echo $i # function variable, # потому что происходит сокрытие глобальной переменной при объявлении одноимённой локальной. } i="script variable" echo $i # script variable suncTest echo $i # script variable - в скрипте осталась глобальная переменная. # Массивы в функции по умолчанию как раз локальные. Чтобы сделать их глобальными, нужно объявлять их с ключом -g test() { declare -g files=(Documents/*.txt) } ===== Файлы, каталоги ===== mkdir -p /tmp/g/d/f/s/h/r/e/e/directory # создать каталог со всеми вышестоящими touch /tmp/file.txt # создать файл # Проверка существования каталога (файла - ключ -f) if [ -d "$dir" ] then echo "$dir exists" else echo "$dir doesn't exist" fi https://linuxhint.com/3hr_bash_tutorial ==== Учётки, группы ==== # Посмотреть, кто заходил в систему last ==== Работа с файлами ==== # Поиск расширений файлов find . -type f -name '*.*' | sed 's|.*\.||' | sort -u ===== Примеры ===== Если делитель 3,5 или 7, то выводить соответственно Pling, Plang или Plong. Если заданное число-аргумент делится на несколько делителей сразу, Соединять слова. Если заданное число не делится ни на один из делителей, выводить само число. ([[https://en.wikipedia.org/wiki/Fizz_buzz|Fizz buzz]]) #!/bin/bash array[3]="i" array[5]="a" array[7]="o" main() { for f in "${!array[@]}"; do if ! (( $1 % $f )); then str+="Pl${array[$f]}ng" fi done echo "$str" } result=$(main "$@") if [ -n "$result" ]; then echo "$result" else echo "$@" fi ==== Сравнение строк и вывод кол-ва различающихся символов ==== if [ $# -lt 2 ]; then echo "Usage: $0 "; exit 1; fi if [ ${#1} -ne ${#2} ]; then echo "strands must be of equal length"; exit 1; fi for (( c=0; c<=${#1}; c++ )); do if [[ "${1:$c:1}" != "${2:$c:1}" ]]; then ((d++)) fi done echo "${d:-0}" ==== Акроним из строки ==== # Ненужное echo: # array=$(echo "${@//[^\'[:alpha:]]/ }") array="${@//[^\'[:alpha:]]/ }" # Учтён также апостроф for i in ${array[@]}; do declare -u str+="${i:0:1}" done echo "$str" ==== Проверка числа, является ли оно числом Армстронга ==== for (( c=0; c<${#1}; c++ )); do d=$((d + (${1:$c:1} ** ${#1}))) # $d в выражении не нужен done if [[ $d -eq $* ]]; then echo "true" else echo "false" fi [[https://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%BE_%D0%90%D1%80%D0%BC%D1%81%D1%82%D1%80%D0%BE%D0%BD%D0%B3%D0%B0|Число Армстронга]]\\ https://www.shellcheck.net/wiki/SC2004\\ https://www.shellcheck.net/wiki/SC2199 ==== Проверка, является ли предложение панграммой ==== # Assigning an array to a string! Assign as array, or use * instead of @ to concatenate. declare -l str="$*" for i in {a..z}; do # Remove quotes from right-hand side of =~ to match as a regex rather than literally. if [[ ! "$str" =~ $i ]]; then echo "false" exit fi done echo "true" ==== Ответы на разные обращения ==== В конструкции case сверху надо указывать наиболее специфичные конструкции. shopt -s extglob str="${*//[^[:alnum:]?]}" case "$str" in *([^[:lower:]])+([[:upper:]])*([^[:lower:]])\?) echo "YELL QUESTION?";; *([^[:lower:]])+([[:upper:]])*([^[:lower:]])) echo "YELL!!!1";; "") echo "Empty string";; *\?) echo "Normal question" ;; *) echo "Something else" ;; esac В case используются не регулярки, а [[https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Pattern-Matching|pattern matching]].\\ ''shopt -s extglob'' - это включение [[https://www.linuxjournal.com/content/bash-extended-globbing|extended globbing]]. ==== Вычисление очков по буквам слова ==== # Quote parameters to tr to prevent glob expansion # Declare and assign separately to avoid masking return values # declare -u str=$(echo "$@" |tr -cd [[:alpha:]]) # Don't use [] around ranges in tr, it replaces literal square brackets. str=$(echo "${@^^}" |tr -cd '[:alpha:]') num=0 # Условие - ноль тоже нужно вывести, если ввод пустой, а так можно и без этой переменной for (( c=0; c<=${#str}; c++ )); do case ${str:$c:1} in A|E|I|O|U|L|N|R|S|T) num=$((num+1));; D|G) num=$((num+2));; B|C|M|P) num=$((num+3));; F|H|V|W|Y) num=$((num+4));; K) num=$((num+5));; J|X) num=$((num+8));; Q|Z) num=$((num+10));; esac done echo "$num" https://www.shellcheck.net/wiki/SC2060\\ https://www.shellcheck.net/wiki/SC2021 ==== Подсчёт зёрен на клетках шахматной доски ==== ''bc'' применяется, потому что bash не может работать с числами больше 263-1. squarecalc() { # Argument mixes string and array. Use * or separate argument. bc <<< "2^($@-1)" } # Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @). if [[ $@ -ge 1 && $@ -le 64 ]]; then # Double quote array expansions to avoid re-splitting elements. # Useless echo? Instead of 'echo $(cmd)', just use 'cmd'. echo $(squarecalc "$@") # Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @). elif [[ $@ == "total" ]]; then t=0 for ((c=1; c<=64; c++)); do d=$(squarecalc $c) t=$(bc <<< "$t+$d") done echo "$t" else echo "Error: invalid input" exit 1 fi https://www.shellcheck.net/wiki/SC2068\\ https://www.shellcheck.net/wiki/SC2145\\ https://www.shellcheck.net/wiki/SC2199\\ https://www.shellcheck.net/wiki/SC2046\\ https://www.shellcheck.net/wiki/SC2086\\ https://www.shellcheck.net/wiki/SC2005 ==== Алгоритм Луна (проверка номера кредитки) ==== check() { # Идём по знакам по порядку for ((c=0;c<${#1};c++)) do num=${1:$c:1} # Если номер знака нечётный (здесь 0 даёт false), то умножаем его на 2, # $/${} is unnecessary on arithmetic variables. if ! (($c % 2)); then num=$((num*2)) # а если после этого он стал больше 9, вычитаем 9 if [[ $num -gt 9 ]]; then num=$((num-9)) fi fi # Добавляем цифру в строку с плюсом dbl+="$num+" done # В конце 0, чтобы строка не заканчивалась плюсом echo "${dbl}0" } # Убрать пробелы cn="${*//[[:blank:]]}" # Если знаков меньше 2 и есть что-то кроме цифр - выход # Use -n instead of ! -z. if [[ "${#cn}" -lt 2 || ! -z "${cn//[[:digit:]]}" ]]; then echo "false"; exit; fi # Если знаков нечётное кол-во - добавляем 0 в начало if ((${#cn} % 2)); then cn="0$cn" terms=$(check "$cn") else terms=$(check "$cn") fi # Если сумма знаков кратна 10 (здесь 0 даёт false), то номер правильный # $/${} is unnecessary on arithmetic variables. if ! ((($terms) % 10)); then echo "true" else echo "false" fi https://www.shellcheck.net/wiki/SC2004\\ ==== Шифр Атбаш ==== При кодировании (''./script.sh encode word word'') - замещение букв зеркальными по алфавиту, цифры остаются, потом строка разбивается на сегменты по 5 символов.\\ При декодировании (''./script.sh decode dliwd liw'') - то же, только сегменты лепятся обратно в сплошную строку (''wordword''). declare -a az=( {a..z} ) declare -a za=( {z..a} ) str="${*:2}" str=$(echo "${str,,}" |tr -cd '[:alnum:]') declare -A array for ((c=0;c<"${#az[@]}";c++)); do array[${az[$c]}]="${za[$c]}" done for n in {0..9}; do array[$n]=$n; done for ((c=0;c<${#str};c++)); do result+="${array[${str:$c:1}]}" done case $1 in encode) result=$(echo "$result" |fold -w5) && echo $result;; decode) echo "$result";; esac ==== Строка наоборот ==== input="$*" for ((c=${#input};c>=0;c--)); do str+="${input:$c:1}" done echo "$str" ==== Проверка на високосный год ==== Год должен делиться на 4, исключая те, что делятся на 100 (но если этот исключённый делится на 400, то включить). # Аргумент должен присутствовать, и в нём не должно быть ничего, кроме цифр if [[ -z $* || -n ${*//[[:digit:]]} ]]; then echo "Usage: $0 "; exit 1; fi if (( $*%4==0 && ($*%100!=0 || $*%400==0) )); then echo "true" else echo "false" fi ==== Число резисторов ==== declare -A array=( [black]=0 [brown]=1 [red]=2 [orange]=3 [yellow]=4 [green]=5 [blue]=6 [violet]=7 [grey]=8 [white]=9 ) # Непонятно, как сравнивать сразу с 2 значениями # Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @). if [[ ! ${!array[@]} =~ $1 || ! ${!array[@]} =~ $2 ]]; then echo "invalid color" exit 1 fi # 1-е число не должно быть нулём echo "${array[$1]//0}${array[$2]}" https://www.shellcheck.net/wiki/SC2199 Вариант 2 - резистора три, третий добавляет соответствующее кол-во нулей. От нулей зависит потом - кило- мега- гига- или просто омы. shopt -s extglob declare -A colors=( [black]=0 [brown]=1 [red]=2 [orange]=3 [yellow]=4 [green]=5 [blue]=6 [violet]=7 [grey]=8 [white]=9 ) zero() { for ((c=1;c<=$1;c++)); do echo -n "0" done } # Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @). if [[ ! ${!colors[@]} =~ $1 || ! ${!colors[@]} =~ $2 || ! ${!colors[@]} =~ $3 ]]; then echo "Wrong color" exit 1 fi num="${colors[$1]}${colors[$2]}$(zero "${colors[$3]}")" num="${num##+(0)}" case "$num" in *000000000) echo "${num//000000000} gigaohms" ;; *000000) echo "${num//000000} megaohms" ;; *000) echo "${num//000} kiloohms" ;; *(0)) echo "0 ohms" ;; *) echo "$num ohms" ;; esac ==== Последовательность слов ==== В соответствии с разрядами двоичного числа. Старший разряд - перевернуть массив. bin=({0..1}{0..1}{0..1}{0..1}{0..1}) n="${bin[$1]}" if [[ ${n:4:1} -eq 1 ]]; then fw+=("wink"); fi if [[ ${n:3:1} -eq 1 ]]; then fw+=("double blink"); fi if [[ ${n:2:1} -eq 1 ]]; then fw+=("close your eyes"); fi if [[ ${n:1:1} -eq 1 ]]; then fw+=("jump"); fi if [[ ${n:0:1} -eq 1 ]]; then for i in $(seq 1 ${#fw[@]}); do rev+=("${fw[-$i]}") done fi # Разделитель IFS работает только с [*], с [@] - нет. if [[ -n $rev ]]; then IFS=, ; echo "${rev[*]}" else IFS=, ; echo "${fw[*]}" fi ==== Дартс ==== Радиус -1-1 - 10 очков, -5-5 - 5 очков, -10-10 - 1 очко. Допускаются дроби: -0.4, 5.5 и т. п.\\ x2 + y2 %%<=%% R2\\ https://stackoverflow.com/questions/481144/equation-for-testing-if-a-point-is-inside-a-circle if [[ $# -ne 2 || -n ${1//[0-9\.-]} || -n ${2//[0-9\.-]} ]]; then echo "error" exit 1 fi coord() { echo "$1^2 + $2^2 <= $3" |bc } if [[ $(coord $1 $2 1) -eq 1 ]]; then echo "10" exit 0 elif [[ $(coord $1 $2 25) -eq 1 ]]; then echo "5" exit 0 elif [[ $(coord $1 $2 100) -eq 1 ]]; then echo "1" exit 0 else echo "0" fi ==== Проверка правильности закрытия кавычек в строке ==== Участвуют кавычки ''( ) [ ] { }''. declare -A ref=([\)]='(' [\]]='[' [\}]='{') str="${*//[^\[\]\{\}\(\)]}" for ((c=0;c<${#str};c++)); do cur=${str:$c:1} case $cur in \{|\(|\[) a+=("$cur") ;; # Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @). *) if [[ -z ${a[@]} ]]; then echo "false" exit else # Quote the right-hand side of != in [[ ]] to prevent glob matching. if [[ ${ref[$cur]} != ${a[-1]} ]]; then echo "false" exit else unset 'a[-1]' fi fi ;; esac done # Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @). if [[ -z ${a[@]} ]]; then echo "true" else echo "false" fi https://www.shellcheck.net/wiki/SC2199\\ https://www.shellcheck.net/wiki/SC2053 Гораздо более удобный вариант удаления последнего символа - ''a=${a%?}''. Соответственно, можно обойтись без массива ''${a[@]}'', как и без вложенных ''if'' (но у меня почему-то не работало ИЛИ). ==== Гвоздь и подкова ==== Генератор пословицы из набора слов на входе. a=("$@") # Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @). if [[ -z ${a[@]} ]]; then exit; fi for ((c=0;c<${#a[@]}-1;c++)); do echo "For want of a ${a[$c]} the ${a[$c+1]} was lost." done echo "And all for the want of a ${a[0]}." For want of a nail the shoe was lost.\\ For want of a shoe the horse was lost.\\ For want of a horse the rider was lost.\\ For want of a rider the message was lost.\\ For want of a message the battle was lost.\\ For want of a battle the kingdom was lost.\\ And all for the want of a horseshoe nail.\\ [[https://en.wikipedia.org/wiki/For_Want_of_a_Nail|For Want of a Nail]]