Инструменты пользователя

Инструменты сайта


learning:bash

Содержание

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

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

Функции

Рекомендации:

  1. Имя функции должно сообщать читателю кода, что она делает.
  2. В функциях нужно использовать только локальные переменные. В ином случае конфликт имён локальных и глобальных переменных решается соглашением об их именовании.
  3. Не надо использовать глобальные переменные в функциях. Вместо этого значение глобальной переменной передаётся в функцию через параметр.
  4. Не надо использовать ключевое слово 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. Если заданное число-аргумент делится на несколько делителей сразу, Соединять слова. Если заданное число не делится ни на один из делителей, выводить само число. (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

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

learning/bash.txt · Последнее изменение: 30.07.2024 19:21 — 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki