====== 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]]