Co to Jest Bash ?

Bash to tzw. powłoka systemu Linux. To nic innego jak program, który umożliwia nam komunikację z systemem. Są różne powłoki dla systemu Linux ale Bash jest zdecydowanie najpopularniejszy. Dla niezliczonej ilości użytkowników jest on podstawowym narzędziem pracy.

Co mogę zrobić za pomocą powłoki systemu BASH ?

Wszystko! Łatwiej byłoby napisać czego się nie da zrobić.

Pomyśl o dowolnym zadaniu, które w systemie Linux zajmuje sporo czasu. I pomyśl teraz, że to zadanie możesz łatwo zautomatyzować za pomocą Bash.

Za pomocą skryptów Bash możesz na przykład:

  • pisać własne komendy
  • wykonywać obliczenia
  • operacje na plikach
  • automatyzować zadania

I wiele więcej :)

Popatrzmy na prosty przykład. Wyobraź sobie, że często zmieniasz nazwy plików w katalogu. Ręczna zmian nazw przy dużej ilości plików jest pracochłonna. Wystarczy napisać prosty skrypt, który będzie to robił automatycznie. Będzie w stanie w parę sekund zmienić nazwy tysięcy plików.

Jeśli nauczysz się podstaw skryptowania to praca w Linuxie będzie po prostu łatwiejsza, szybsza i dużo przyjemniejsza.

Jak zacząć pisać skrypty powłoki?

Poniżej kilka idealnych stron, aby zacząć przygodę z skryptami powłoki:

Przykładowy skrypt

#!/usr/bin/env bash

name="John"
echo "Hello $name!"

Zmienne

name="John"
echo $name       # see below
echo "$name"
echo "${name}!"

Wykonanie warunkowe

git commit && git push
git commit || echo "Commit failed"

Warunki

if [[ -z "$string" ]]; then
  echo "String is empty"
elif [[ -n "$string" ]]; then
  echo "String is not empty"
fi

Typ string

name="John"
echo "Hi $name"  #=> Hi John
echo 'Hi $name'  #=> Hi $name

Wykonanie poleceń powłoki

echo "I'm in $(pwd)"
echo "I'm in `pwd`" 

Strict mode

set -euo pipefail
IFS=$'\n\t'

Funkcje

get_name() {
  echo "John"
}

echo "You are $(get_name)"

Definicje funkcji

myfunc() {
    echo "hello $1"
}

# To samo, ale inny zapis
function myfunc() {
    echo "hello $1"
}

myfunc "John"

Zwracanie wartości

myfunc() {
    local myresult='some value'
    echo "$myresult"
}

result=$(myfunc)

Argumenty funkcji

$# 	# Liczba argumentów
$* 	# Wszystkie argumenty pozycyjne (jako jedno słowo)
$@ 	# Wszystkie argumenty pozycyjne (jako oddzielne ciągi znaków)
$1 	# Pierwszy argument
$_ 	# Ostatni argument poprzedniego polecenia
${PIPESTATUS[n]} 	# Wartość zwrócona przez polecenia w potoku (tablica)

Błędy

myfunc() {
  return 1
}

if myfunc; then
  echo "success"
else
  echo "failure"
fi

Rozwijanie nawiasów klamrowych

echo {A,B}.js

{A,B}    # To samo co A B
{A,B}.js    # To samo co A.js B.js
{1..5}    # To samo co 1 2 3 4 5

Rozwinięcia parametrów

name="John"
echo "${name}"
echo "${name/J/j}"    #=> "john" (zamiana)
echo "${name:0:2}"    #=> "Jo" (wycinanie)
echo "${name::2}"     #=> "Jo" (wycinanie)
echo "${name::-1}"    #=> "Joh" (wycinanie)
echo "${name:(-1)}"   #=> "n" (wycinanie od prawej strony)
echo "${name:(-2):1}" #=> "h" (wycinanie od prawej strony)
echo "${food:-Cake}"  #=> $food lub "Cake"

length=2
echo "${name:0:length}"  #=> "Jo"

Zobacz: Rozwinięcia parametrów

str="/path/to/foo.cpp"
echo "${str%.cpp}"    # /path/to/foo
echo "${str%.cpp}.o"  # /path/to/foo.o
echo "${str%/*}"      # /path/to

echo "${str##*.}"     # cpp (rozszerzenie)
echo "${str##*/}"     # foo.cpp (bazowa nazwa)

echo "${str#*/}"      # path/to/foo.cpp
echo "${str##*/}"     # foo.cpp

echo "${str/foo/bar}" # /path/to/bar.cpp

str="Hello world"
echo "${str:6:5}"   # "world"
echo "${str: -5:5}"  # "world"

src="/path/to/foo.cpp"
base=${src##*/}   #=> "foo.cpp" (bazowa nazwa)
dir=${src%$base}  #=> "/path/to/" (ścieżka)

Zastępowanie wartości zmiennych

${foo%suffix}    # Usunięcie sufiksu
${foo#prefix}    # Usunięcie prefiksu
${foo%%suffix}    # Usunięcie długiego sufiksu
${foo/%suffix}    # Usunięcie długiego sufiksu
${foo##prefix}    # Usunięcie długiego prefiksu
${foo/#prefix}    # Usunięcie długiego prefiksu
${foo/from/to}    # Zamiana pierwszego dopasowania
${foo//from/to}    # Zamiana wszystkich dopasowań
${foo/%from/to}    # Zamiana sufiksu
${foo/#from/to}    # Zamiana prefiksu

Komentarze

# Po

jedynczy komentarz
: '
To jest
komentarz
wielu linii
'

Substrings

${foo:0:3}
${foo:(-3):3}

Długość

${#foo}    # Długość zmiennej $foo

Domyślne wartości

${foo:-val}    # $foo lub val, jeśli nieustawiony (lub pusty)
${foo:=val}    # Ustaw $foo na val, jeśli nieustawiony (lub pusty)
${foo:+val}    # val, jeśli $foo jest ustawiony (i niepusty)
${foo:?message}    # Wyświetl komunikat o błędzie i zakończ, jeśli $foo jest nieustawiony (lub pusty)

Użycie dwukropka (:), usuwa sprawdzanie (nie)ustawienia, np. ${foo-val} zwróci val, jeśli $foo jest nieustawiony, w przeciwnym razie $foo.

Pętle

Pętla for

for i in /etc/rc.*; do
  echo "$i"
done

Pętla for jak w języku C

for ((i = 0 ; i < 100 ; i++)); do
  echo "$i"
done

Pętla while czytająca linie

while read -r line; do
  echo "$line"
done <file.txt

Nieskończona pętla

while true; do
  ···
done

Zakresy

for i in {1..5}; do
    echo "Welcome $i"
done
for i in {5..50..5}; do
    echo "Welcome $i"
done

Warunki

[[ -z STRING ]]    # Pusty ciąg znaków
[[ -n STRING ]]    # Niepusty ciąg znaków
[[ STRING == STRING ]]    # Równa się
[[ STRING != STRING ]]    # Nie równa się
[[ NUM -eq NUM ]]    # Równa się
[[ NUM -ne NUM ]]    # Nie równa się
[[ NUM -lt NUM ]]    # Mniejsze niż
[[ NUM -le NUM ]]    # Mniejsze lub równe
[[ NUM -gt NUM ]]    # Większe niż
[[ NUM -ge NUM ]]    # Większe lub równe
[[ STRING =~ STRING ]]    # Dopasowanie wyrażenia regularnego
(( NUM < NUM ))    # Warunki liczbowe
[[ -o noclobber ]]    # Jeśli opcja OPTIONNAME jest włączona
[[ ! EXPR ]]    # Negacja
[[ X && Y ]]    # I
[[ X || Y ]]    # Lub

Warunki na plikach

[[ -e FILE ]]    # Istnieje
[[ -r FILE ]]    # Możliwe do odczytu
[[ -h FILE ]]    # Slink
[[ -d FILE ]]    # Katalog
[[ -w FILE ]]    # Możliwe do zapisu
[[ -s FILE ]]    # Rozmiar jest > 0 bajtów
[[ -f FILE ]]    # Plik
[[ -x FILE ]]    # Wykonywalny
[[ FILE1 -nt FILE2 ]]    # 1 jest nowszy niż 2
[[ FILE1 -ot FILE2 ]]    # 2 jest nowszy niż 1
[[ FILE1 -ef FILE2 ]]    # Te same pliki

Przykłady warunków

# String
if [[ -z "$string" ]]; then
  echo "String is empty"
elif [[ -n "$string" ]]; then
  echo "String is not empty"
else
  echo "This never happens"
fi

# Combinations
if [[ X && Y ]]; then
  ...
fi

# Equal
if [[ "$A" == "$B" ]]

# Regex
if [[ "A" =~ . ]]

if (( $a < $b )); then
   echo "$a is smaller than $b"
fi

if [[ -e "file.txt" ]]; then
  echo "file exists"
fi

Tablice

Deklaracje

Fruits=('Apple' 'Banana' 'Orange')

Fruits[0]="Apple"
Fruits[1]="Banana"
Fruits[2]="Orange"

Praca z tablicami

echo "${Fruits[0]}"           # Element #0
echo "${Fruits[-1]}"          # Ostatni element
echo "${Fruits[@]}"           # Wszystkie elementy, oddzielone spacją
echo "${#Fruits[@]}"          # Liczba elementów
echo "${#Fruits}"             # Długość pierwszego elementu
echo "${#Fruits[3]}"          # Długość N-tego elementu
echo "${Fruits[@]:3:2}"       # Zakres (od pozycji 3, długość 2)
echo "${!Fruits[@]}"          # Klucze wszystkich elementów, oddzielone spacją

Operacje na tablicach

Fruits=("${Fruits[@]}" "Watermelon")    # Dodaj
Fruits+=('Watermelon')                  # To samo, dodaj
Fruits=( "${Fruits[@]/Ap*/}" )          # Usuń według dopasowania wyrażenia regularnego
unset Fruits[2]                         # Usuń jeden element
Fruits=("${Fruits[@]}")                 # Duplikuj
Fruits=("${Fruits[@]}" "${Veggies[@]}") # Połącz dwie tablice
lines=(`cat "logfile"`)                 # Odczytaj z pliku

Iteracja po tablicy

for i in "${arrayName[@]}"; do
  echo "$i"
done

Słowniki

Deklaracje

declare -A sounds

sounds[dog]="bark"
sounds[cow]="moo"
sounds[bird]="tweet"
sounds[wolf]="howl"

Deklaruje dźwięk jako obiekt słownika (czyli tablicy asocjacyjnej).

Praca ze słownikami

echo "${sounds[dog]}"    # Dźwięk ps

a
echo "${sounds[@]}"      # Wszystkie wartości
echo "${!sounds[@]}"     # Wszystkie klucze
echo "${#sounds[@]}"     # Liczba elementów
unset sounds[dog]        # Usuń klucz psa

Iteracja po słowniku

# Iteracja po wartościach
for val in "${sounds[@]}"; do
  echo "$val"
done

# Iteracja po kluczach
for key in "${!sounds[@]}"; do
  echo "$key"
done

Opcje

set -o noclobber    # Unikaj nadpisywania plików (echo "hi" > foo)
set -o errexit      # Wyjdź po błędzie, unikając kaskadowych błędów
set -o pipefail     # Wyświetl ukryte błędy
set -o nounset      # Ujawnia nieustawione zmienne

Globalne opcje

shopt -s nullglob      # Nie pasujące globs są usuwane ('*.foo' => '')
shopt -s failglob      # Nie pasujące globs generują błędy
shopt -s nocaseglob    # Wielkość liter w globs ignorowana
shopt -s dotglob       # Wilcardy pasują do plików z kropką ("*.sh" => ".foo.sh")
shopt -s globstar      # Pozwala na ** dla rekurencyjnych dopasowań ('lib/**/*.rb' => 'lib/a/b/c.rb')

Ustaw GLOBIGNORE jako dwukropkowo oddzieloną listę wzorców do pominięcia w dopasowaniach globalnych.

Obliczenia numeryczne

$((a + 200))      # Dodaj 200 do $a

$(($RANDOM%200))  # Losowa liczba od 0 do 199

declare -i count  # Deklaruj zmienną jako liczbę całkowitą
count+=1          # Inkrementuj zmienną count

Podpowłoki (subshells)

(cd somedir; echo "Teraz jestem w: $PWD")
pwd  # Nadal w pierwszym katalogu

Sprawdzanie polecenia

command -V cd
#=> "cd to funkcja/alias/cokolwiek"

Trap errors

trap 'echo Błąd w linii $LINENO' ERR

lub

traperr() {
  echo "BŁĄD: ${BASH_SOURCE[1]} w linii ${BASH_LINENO[0]}"
}

set -o errtrace
trap traperr ERR

Case/switch

case "$1" in
  start | up)
    vagrant up
    ;;

  *)
    echo "Użycie: $0 {start|stop|ssh}"
    ;;
esac

Źródło względne

source "${0%/*}/../share/foo.sh"

Pobieranie opcji

while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do case $1 in
  -V | --version )
    echo "$version"
    exit
    ;;
  -s | --string )
    shift; string=$1
    ;;
  -f | --flag )
    flag=1
    ;;
esac; shift; done
if [[ "$1" == '--' ]]; then shift; fi

Heredoc

cat <<END
hello world
END

Odczytywanie wejścia

echo -n "Czy kontynuować? [y/n]: "
read -r ans
echo "$ans"

Opcja -r wyłącza specjalne znaczenie odwrotnego ukośnika.

Specjalne zmienne

$?    # Status wyjścia ostatniego zadania
$!    # PID ostatniego zadania w tle (tło)
$$    # PID powłoki (aktualnego skryptu)
$0    # Nazwa pliku aktualnego skryptu
$_    # Ostatni argument poprzedniego polecenia
${PIPESTATUS[n]}    # Wartość zwrócona przez potokowe polecenia (tablica)

Zobacz Specjalne parametry.

Powrót do poprzedniego katalogu

pwd    # /home/user/foo
cd bar/
pwd    # /home/user/foo/bar
cd -
pwd    # /home/user/foo

Sprawdzanie wyniku polecenia

if ping -c 1 google.com; then
  echo "Wydaje się, że masz działające połączenie internetowe"
fi

Sprawdzanie za pomocą grep

if grep -q 'foo' ~/.bash_history; then
  echo "W przeszłości wpisałeś 'foo'"
fi