Содержание
Bash
Илья Шпигорь - Программирование на Bash с нуля
Bash scripting cheatsheet
Learn X in Y minutes: Bash
BashGuide
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
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 |
Классы символов
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.
Прочее
Посылка данных в другой скрипт
# 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
Учётки, группы
# Посмотреть, кто заходил в систему last
Работа с файлами
# Поиск расширений файлов find . -type f -name '*.*' | sed 's|.*\.||' | sort -u
Примеры
Если делитель 3,5 или 7, то выводить соответственно Pling, Plang или Plong. Если заданное число-аргумент делится на несколько делителей сразу, Соединять слова. Если заданное число не делится ни на один из делителей, выводить само число. (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 <string1> <string2>"; 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://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 используются не регулярки, а pattern matching.
shopt -s extglob
- это включение 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
Шифр Атбаш
При кодировании (./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 <year>"; 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.
For Want of a Nail