docs: add bash progress bar article
This commit is contained in:
parent
d73dd9d0fa
commit
97b9cb24ad
14 changed files with 1160 additions and 0 deletions
18
content/articles/2024/bash_printf/files/script1_ping.sh
Normal file
18
content/articles/2024/bash_printf/files/script1_ping.sh
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
PING_ITER=16
|
||||||
|
|
||||||
|
# shellcheck disable=2317
|
||||||
|
command() {
|
||||||
|
local -r host=${1?"first parameter must be an host"}
|
||||||
|
local -r iteration=${2?"second parameter must be an iteration number"}
|
||||||
|
local -r cmd=(ping -c "${iteration}" "${host}")
|
||||||
|
|
||||||
|
"${cmd[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
command "aquilenet.fr" "$PING_ITER"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
|
exit 0
|
24
content/articles/2024/bash_printf/files/script2_getoutput.sh
Normal file
24
content/articles/2024/bash_printf/files/script2_getoutput.sh
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
PING_ITER=16
|
||||||
|
|
||||||
|
# shellcheck disable=2317
|
||||||
|
command() {
|
||||||
|
local -r host=${1?"first parameter must be an host"}
|
||||||
|
local -r iteration=${2?"second parameter must be an iteration number"}
|
||||||
|
local -r cmd=(ping -c "${iteration}" "${host}")
|
||||||
|
|
||||||
|
"${cmd[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_output() {
|
||||||
|
while read -r line; do
|
||||||
|
printf "out: %s\n" "${line}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
command "aquilenet.fr" "$PING_ITER" > >(parse_output)
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
|
exit 0
|
29
content/articles/2024/bash_printf/files/script3_rematch.sh
Normal file
29
content/articles/2024/bash_printf/files/script3_rematch.sh
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
PING_ITER=16
|
||||||
|
|
||||||
|
# shellcheck disable=2317
|
||||||
|
command() {
|
||||||
|
local -r host=${1?"first parameter must be an host"}
|
||||||
|
local -r iteration=${2?"second parameter must be an iteration number"}
|
||||||
|
local -r cmd=(ping -c "${iteration}" "${host}")
|
||||||
|
|
||||||
|
"${cmd[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_output() {
|
||||||
|
while read -r line; do
|
||||||
|
if [[ "$line" =~ icmp_seq=([[:digit:]]{1,}).*time=(.*) ]]; then
|
||||||
|
printf "séquence: %s sur %s temps:%s\n" \
|
||||||
|
"${BASH_REMATCH[1]}" \
|
||||||
|
"$PING_ITER" \
|
||||||
|
"${BASH_REMATCH[2]}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
command "aquilenet.fr" "$PING_ITER" > >(parse_output)
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
|
exit 0
|
42
content/articles/2024/bash_printf/files/script4_progress.sh
Normal file
42
content/articles/2024/bash_printf/files/script4_progress.sh
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=2317
|
||||||
|
PING_ITER=16
|
||||||
|
PROGRESS_BAR_CHAR=(█ ▒)
|
||||||
|
|
||||||
|
command() {
|
||||||
|
local -r host=${1?"first parameter must be an host"}
|
||||||
|
local -r iteration=${2?"second parameter must be an iteration number"}
|
||||||
|
local -r cmd=(ping -c "${iteration}" "${host}")
|
||||||
|
|
||||||
|
"${cmd[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_progressbar() {
|
||||||
|
local -r progress=${1?"progress is mandatory"}
|
||||||
|
local -r total=${2?"total is mandatory"}
|
||||||
|
local progress_segment
|
||||||
|
local todo_segment
|
||||||
|
|
||||||
|
printf -v progress_segment "%${progress}s" ""
|
||||||
|
printf -v todo_segment "%$((total - progress))s" ""
|
||||||
|
printf >&2 "%s%s\r" \
|
||||||
|
"${progress_segment// /${PROGRESS_BAR_CHAR[0]}}" \
|
||||||
|
"${todo_segment// /${PROGRESS_BAR_CHAR[1]}}"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_output() {
|
||||||
|
while read -r line; do
|
||||||
|
if [[ "$line" =~ icmp_seq=([[:digit:]]{1,}).*time=(.*) ]]; then
|
||||||
|
draw_progressbar "${BASH_REMATCH[1]}" "$PING_ITER"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
command "aquilenet.fr" "$PING_ITER" > >(parse_output)
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
|
exit 0
|
||||||
|
# [...]
|
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=2317
|
||||||
|
PING_ITER=16
|
||||||
|
PROGRESS_BAR_CHAR=(█ ▒)
|
||||||
|
|
||||||
|
command() {
|
||||||
|
local -r host=${1?"first parameter must be an host"}
|
||||||
|
local -r iteration=${2?"second parameter must be an iteration number"}
|
||||||
|
local -r cmd=(ping -c "${iteration}" "${host}")
|
||||||
|
|
||||||
|
"${cmd[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_progressbar() {
|
||||||
|
local -r progress=${1?"progress is mandatory"}
|
||||||
|
local -r total=${2?"Total elements is mandatory"}
|
||||||
|
local -r info_segment=${3:""}
|
||||||
|
local progress_segment
|
||||||
|
local todo_segment
|
||||||
|
|
||||||
|
printf -v progress_segment "%${progress}s" ""
|
||||||
|
printf -v todo_segment "%$((total - progress))s" ""
|
||||||
|
printf >&2 "%s%s%s\r" \
|
||||||
|
"${info_segment}" \
|
||||||
|
"${progress_segment// /${PROGRESS_BAR_CHAR[0]}}" \
|
||||||
|
"${todo_segment// /${PROGRESS_BAR_CHAR[1]}}"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_output() {
|
||||||
|
while read -r line; do
|
||||||
|
if [[ "$line" =~ icmp_seq=([[:digit:]]{1,}).*time=(.*) ]]; then
|
||||||
|
draw_progressbar \
|
||||||
|
"${BASH_REMATCH[1]}" \
|
||||||
|
"$PING_ITER" \
|
||||||
|
"Ping in progress (time: ${BASH_REMATCH[2]}) "
|
||||||
|
elif [[ "$line" =~ ^PING(.*\(.*\)).* ]]; then
|
||||||
|
printf "Launch ping command to %s with %d iterations\n" \
|
||||||
|
"${BASH_REMATCH[1]}" \
|
||||||
|
"$PING_ITER"
|
||||||
|
elif [[ "$line" =~ .*packets\ transmitted.* ]]; then
|
||||||
|
printf "%s\n" "$line"
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
command "aquilenet.fr" "$PING_ITER" > >(parse_output)
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
|
exit 0
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=2317
|
||||||
|
PING_ITER=16
|
||||||
|
PROGRESS_BAR_CHAR=(█ ▒)
|
||||||
|
|
||||||
|
command() {
|
||||||
|
local -r host=${1?"first parameter must be an host"}
|
||||||
|
local -r iteration=${2?"second parameter must be an iteration number"}
|
||||||
|
local -r cmd=(ping -c "${iteration}" "${host}")
|
||||||
|
|
||||||
|
"${cmd[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_progressbar() {
|
||||||
|
local -r progress=${1?"progress is mandatory"}
|
||||||
|
local -r total=${2?"total elements is mandatory"}
|
||||||
|
local -r info_segment=${3:""}
|
||||||
|
local progress_segment
|
||||||
|
local todo_segment
|
||||||
|
|
||||||
|
local -r bar_size=$((COLUMNS - ${#info_segment}))
|
||||||
|
local -r progress_ratio=$((progress * 100 / total))
|
||||||
|
local -r progress_segment_size=$((bar_size * progress_ratio / 100))
|
||||||
|
local -r todo_segment_size=$((bar_size - progress_segment_size))
|
||||||
|
|
||||||
|
printf -v progress_segment "%${progress_segment_size}s" ""
|
||||||
|
printf -v todo_segment "%${todo_segment_size}s" ""
|
||||||
|
|
||||||
|
printf >&2 "%s%s%s\r" \
|
||||||
|
"${info_segment}" \
|
||||||
|
"${progress_segment// /${PROGRESS_BAR_CHAR[0]}}" \
|
||||||
|
"${todo_segment// /${PROGRESS_BAR_CHAR[1]}}"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_output() {
|
||||||
|
while read -r line; do
|
||||||
|
if [[ "$line" =~ icmp_seq=([[:digit:]]{1,}).*time=(.*) ]]; then
|
||||||
|
draw_progressbar \
|
||||||
|
"${BASH_REMATCH[1]}" \
|
||||||
|
"$PING_ITER" \
|
||||||
|
"Ping in progress (time: ${BASH_REMATCH[2]}) "
|
||||||
|
elif [[ "$line" =~ ^PING(.*\(.*\)).* ]]; then
|
||||||
|
printf "Launch ping command to %s with %d iterations\n" \
|
||||||
|
"${BASH_REMATCH[1]}" \
|
||||||
|
"$PING_ITER"
|
||||||
|
elif [[ "$line" =~ .*packets\ transmitted.* ]]; then
|
||||||
|
printf "%s\n" "$line"
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
COLUMNS=$(tput cols)
|
||||||
|
command "aquilenet.fr" "$PING_ITER" > >(parse_output)
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
|
exit 0
|
69
content/articles/2024/bash_printf/files/script7_sigwinch.sh
Normal file
69
content/articles/2024/bash_printf/files/script7_sigwinch.sh
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=2317
|
||||||
|
PING_ITER=64
|
||||||
|
PROGRESS_BAR_CHAR=(█ ▒)
|
||||||
|
|
||||||
|
command() {
|
||||||
|
local -r host=${1?"first parameter must be an host"}
|
||||||
|
local -r iteration=${2?"second parameter must be an iteration number"}
|
||||||
|
local -r cmd=(ping -c "${iteration}" "${host}")
|
||||||
|
|
||||||
|
"${cmd[@]}"
|
||||||
|
}
|
||||||
|
trap change_column_size WINCH
|
||||||
|
|
||||||
|
draw_progressbar() {
|
||||||
|
local -r progress=${1?"progress is mandatory"}
|
||||||
|
local -r total=${2?"total elements is mandatory"}
|
||||||
|
local -r info_segment=${3:""}
|
||||||
|
local progress_segment
|
||||||
|
local todo_segment
|
||||||
|
|
||||||
|
local -r bar_size=$((COLUMNS - ${#info_segment}))
|
||||||
|
local -r progress_ratio=$((progress * 100 / total))
|
||||||
|
local -r progress_segment_size=$((bar_size * progress_ratio / 100))
|
||||||
|
local -r todo_segment_size=$((bar_size - progress_segment_size))
|
||||||
|
|
||||||
|
printf -v progress_segment "%${progress_segment_size}s" ""
|
||||||
|
printf -v todo_segment "%${todo_segment_size}s" ""
|
||||||
|
|
||||||
|
printf >&2 "%s%s%s\r" \
|
||||||
|
"${info_segment}" \
|
||||||
|
"${progress_segment// /${PROGRESS_BAR_CHAR[0]}}" \
|
||||||
|
"${todo_segment// /${PROGRESS_BAR_CHAR[1]}}"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_output() {
|
||||||
|
trap change_column_size WINCH
|
||||||
|
while read -r line; do
|
||||||
|
if [[ "$line" =~ icmp_seq=([[:digit:]]{1,}).*time=(.*) ]]; then
|
||||||
|
|
||||||
|
draw_progressbar \
|
||||||
|
"${BASH_REMATCH[1]}" \
|
||||||
|
"$PING_ITER" \
|
||||||
|
"Ping in progress (time: ${BASH_REMATCH[2]}) "
|
||||||
|
elif [[ "$line" =~ ^PING(.*\(.*\)).* ]]; then
|
||||||
|
printf "Launch ping command to %s with %d iterations\n" \
|
||||||
|
"${BASH_REMATCH[1]}" \
|
||||||
|
"$PING_ITER"
|
||||||
|
elif [[ "$line" =~ .*packets\ transmitted.* ]]; then
|
||||||
|
printf >&2 "\033[0K\r"
|
||||||
|
printf "%s\n" "$line"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
change_column_size() {
|
||||||
|
printf >&2 "%${COLUMNS}s" ""
|
||||||
|
printf >&2 "\033[0K\r"
|
||||||
|
COLUMNS=$(tput cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
COLUMNS=$(tput cols)
|
||||||
|
command "aquilenet.fr" "$PING_ITER" > >(parse_output)
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
|
exit 0
|
97
content/articles/2024/bash_printf/files/script8_bonus.sh
Normal file
97
content/articles/2024/bash_printf/files/script8_bonus.sh
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ...
|
||||||
|
# shellcheck disable=2317,2059
|
||||||
|
PING_ITER=16
|
||||||
|
|
||||||
|
# Progress bar dedicated variables
|
||||||
|
# We can control how progress bar work
|
||||||
|
PROGRESS_BAR_CHAR=(█ ▒)
|
||||||
|
|
||||||
|
# control which information is displayed"
|
||||||
|
PROGRESS_BAR_DISPLAY_INFO=1
|
||||||
|
PROGRESS_BAR_DISPLAY_COMPL=1
|
||||||
|
|
||||||
|
# Display template
|
||||||
|
# We can control how informations is displayed
|
||||||
|
PROGRESS_BAR_INFO_TEMPLATE=' %s [%2d/%2d] '
|
||||||
|
PROGRESS_BAR_COMPL_TEMPLATE=' %2d%% '
|
||||||
|
PROGRESS_BAR_TEMPLATE='\033[1m%s%s\033[0m%s%s\r'
|
||||||
|
|
||||||
|
command() {
|
||||||
|
local -r host=${1?"first parameter must be an host"}
|
||||||
|
local -r iteration=${2?"second parameter must be an iteration number"}
|
||||||
|
local -r cmd=(ping -c "${iteration}" "${host}")
|
||||||
|
|
||||||
|
"${cmd[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_progressbar() {
|
||||||
|
# function parameters
|
||||||
|
local -r progress=${1?"progress is mandatory"}
|
||||||
|
local -r total=${2?"total elements is mandatory"}
|
||||||
|
local -r info=${3:-"In progress"}
|
||||||
|
|
||||||
|
# function local variables
|
||||||
|
local progress_segment
|
||||||
|
local todo_segment
|
||||||
|
local info_segment=""
|
||||||
|
local compl_segment=""
|
||||||
|
|
||||||
|
if [[ ${PROGRESS_BAR_DISPLAY_INFO:-1} -eq 1 ]]; then
|
||||||
|
printf -v info_segment "${PROGRESS_BAR_INFO_TEMPLATE}" \
|
||||||
|
"$info" "$progress" "$total"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local -r progress_ratio=$((progress * 100 / total))
|
||||||
|
if [[ ${PROGRESS_BAR_DISPLAY_COMPL:-0} -eq 1 ]]; then
|
||||||
|
printf -v compl_segment "${PROGRESS_BAR_COMPL_TEMPLATE}" \
|
||||||
|
"$progress_ratio"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# progress bar construction
|
||||||
|
# calculate each element sizes, bar must fit in ou screen
|
||||||
|
local -r bar_size=$((COLUMNS - ${#info_segment} - ${#compl_segment}))
|
||||||
|
local -r progress_segment_size=$((bar_size * progress_ratio / 100))
|
||||||
|
local -r todo_segment_size=$((bar_size - progress_segment_size))
|
||||||
|
printf -v progress_segment "%${progress_segment_size}s" ""
|
||||||
|
printf -v todo_segment "%${todo_segment_size}s" ""
|
||||||
|
|
||||||
|
printf >&2 "$PROGRESS_BAR_TEMPLATE" \
|
||||||
|
"$info_segment" \
|
||||||
|
"${progress_segment// /${PROGRESS_BAR_CHAR[0]}}" \
|
||||||
|
"${todo_segment// /${PROGRESS_BAR_CHAR[1]}}" \
|
||||||
|
"$compl_segment"
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_output() {
|
||||||
|
trap change_column_size WINCH
|
||||||
|
while read -r line; do
|
||||||
|
if [[ "$line" =~ icmp_seq=([[:digit:]]{1,}).*time=(.*) ]]; then
|
||||||
|
draw_progressbar \
|
||||||
|
"${BASH_REMATCH[1]}" \
|
||||||
|
"$PING_ITER" \
|
||||||
|
"Ping in progess"
|
||||||
|
elif [[ "$line" =~ ^PING(.*\(.*\)).* ]]; then
|
||||||
|
printf "Launch ping command to %s with %d iterations\n" \
|
||||||
|
"${BASH_REMATCH[1]}" \
|
||||||
|
"$PING_ITER"
|
||||||
|
elif [[ "$line" =~ .*packets\ transmitted.* ]]; then
|
||||||
|
printf >&2 "\033[0K\r"
|
||||||
|
printf "%s\n" "$line"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
change_column_size() {
|
||||||
|
printf >&2 "%${COLUMNS}s" ""
|
||||||
|
printf >&2 "\033[0K\r"
|
||||||
|
COLUMNS=$(tput cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
COLUMNS=$(tput cols)
|
||||||
|
command "aquilenet.fr" "$PING_ITER" > >(parse_output)
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
|
exit 0
|
150
content/articles/2024/bash_printf/images/barre_progression.svg
Normal file
150
content/articles/2024/bash_printf/images/barre_progression.svg
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="150.35045mm"
|
||||||
|
height="25.371393mm"
|
||||||
|
viewBox="0 0 150.35045 25.371393"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<title
|
||||||
|
id="title6">Découpage d'une barre de progression</title>
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-29.578274,-38.464996)">
|
||||||
|
<rect
|
||||||
|
style="fill:#cccccc;stroke-width:0.196183"
|
||||||
|
id="rect1"
|
||||||
|
width="82.085648"
|
||||||
|
height="5.4879713"
|
||||||
|
x="86.700256"
|
||||||
|
y="48.571499" />
|
||||||
|
<rect
|
||||||
|
style="fill:#333333;stroke-width:0.139933"
|
||||||
|
id="rect2"
|
||||||
|
width="45.89801"
|
||||||
|
height="5.470767"
|
||||||
|
x="86.700256"
|
||||||
|
y="48.571499" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-weight:200;font-size:3.175px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Ultra-Light';fill:#333333;stroke-width:0.3"
|
||||||
|
x="31.546356"
|
||||||
|
y="52.329395"
|
||||||
|
id="text2"><tspan
|
||||||
|
id="tspan2"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'FiraCode Nerd Font';-inkscape-font-specification:'FiraCode Nerd Font';stroke-width:0.3"
|
||||||
|
x="31.546356"
|
||||||
|
y="52.329395">Reticulating spline (12/42)</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-weight:200;font-size:3.175px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Ultra-Light';fill:#333333;stroke-width:0.3"
|
||||||
|
x="171.58591"
|
||||||
|
y="52.329395"
|
||||||
|
id="text3"><tspan
|
||||||
|
id="tspan3"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'FiraCode Nerd Font';-inkscape-font-specification:'FiraCode Nerd Font';stroke-width:0.3"
|
||||||
|
x="171.58591"
|
||||||
|
y="52.329395">57%</tspan></text>
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#ff2bd5;stroke-width:0.3;stroke-opacity:1"
|
||||||
|
d="m 29.728274,54.571433 v 3.7407 H 87.1745 v -3.7407"
|
||||||
|
id="path3" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#20cc8a;stroke-width:0.3;stroke-opacity:1"
|
||||||
|
d="m 86.878278,47.878793 v -3.7407 h 45.804582 v 3.7407"
|
||||||
|
id="path4" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#b183f5;stroke-width:0.3;stroke-opacity:1"
|
||||||
|
d="m 132.38684,54.571433 v 3.7407 h 36.27987 v -3.7407"
|
||||||
|
id="path5" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-weight:200;font-size:3.175px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Ultra-Light';white-space:pre;inline-size:0;fill:#ff2bd5;fill-opacity:1;stroke:#808080;stroke-width:0.899999;stroke-dasharray:none"
|
||||||
|
x="60.677799"
|
||||||
|
y="154.16701"
|
||||||
|
id="text23635"
|
||||||
|
transform="matrix(1.8930273,0,0,1.8930273,-60.403974,-228.32521)"><tspan
|
||||||
|
id="tspan23633"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'FiraCode Nerd Font Mono';-inkscape-font-specification:'FiraCode Nerd Font Mono Bold';fill:#ff2bd5;fill-opacity:1;stroke:none;stroke-width:0.9"
|
||||||
|
x="60.677799"
|
||||||
|
y="154.16701">1</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-weight:200;font-size:3.175px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Ultra-Light';white-space:pre;inline-size:0;fill:#20cc8a;fill-opacity:1;stroke:#808080;stroke-width:0.899999;stroke-dasharray:none"
|
||||||
|
x="60.677799"
|
||||||
|
y="154.16701"
|
||||||
|
id="text24389"
|
||||||
|
transform="matrix(1.8930273,0,0,1.8930273,-6.4597365,-248.78285)"><tspan
|
||||||
|
id="tspan24387"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'FiraCode Nerd Font Mono';-inkscape-font-specification:'FiraCode Nerd Font Mono Bold';fill:#20cc8a;fill-opacity:1;stroke:none;stroke-width:0.9"
|
||||||
|
x="60.677799"
|
||||||
|
y="154.16701">2</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-weight:200;font-size:3.175px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Ultra-Light';white-space:pre;inline-size:0;fill:#b183f5;fill-opacity:1;stroke:#808080;stroke-width:0.899999;stroke-dasharray:none"
|
||||||
|
x="60.677799"
|
||||||
|
y="154.16701"
|
||||||
|
id="text25264"
|
||||||
|
transform="matrix(1.8930273,0,0,1.8930273,34.889672,-228.11076)"><tspan
|
||||||
|
id="tspan25262"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'FiraCode Nerd Font Mono';-inkscape-font-specification:'FiraCode Nerd Font Mono Bold';fill:#b183f5;fill-opacity:1;stroke:none;stroke-width:0.9"
|
||||||
|
x="60.677799"
|
||||||
|
y="154.16701">3</tspan></text>
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#398eff;stroke-width:0.3;stroke-opacity:1"
|
||||||
|
d="m 168.65607,47.878793 v -3.7407 h 11.12266 v 3.7407"
|
||||||
|
id="path6" />
|
||||||
|
<path
|
||||||
|
style="font-weight:bold;font-size:5.73938px;font-family:'FiraCode Nerd Font';-inkscape-font-specification:'FiraCode Nerd Font Bold';fill:#398eff;fill-opacity:0.992157;stroke-width:0.348625"
|
||||||
|
d="m 189.69326,37.134481 v 0.983053 h 0.40912 v 0.697555 h -0.40912 v 0.856492 h -0.89181 l -0.006,-0.856492 h -1.68944 v -0.618087 l 1.13316,-2.70781 0.79174,0.291384 -0.96539,2.336958 h 0.73287 l 0.1089,-0.983053 z"
|
||||||
|
id="text6"
|
||||||
|
transform="scale(0.92263604,1.083851)"
|
||||||
|
aria-label="4" />
|
||||||
|
</g>
|
||||||
|
<metadata
|
||||||
|
id="metadata6">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:title>Découpage d'une barre de progression</dc:title>
|
||||||
|
<dc:date>2024.05.26</dc:date>
|
||||||
|
<dc:creator>
|
||||||
|
<cc:Agent>
|
||||||
|
<dc:title>ephase</dc:title>
|
||||||
|
</cc:Agent>
|
||||||
|
</dc:creator>
|
||||||
|
<dc:rights>
|
||||||
|
<cc:Agent>
|
||||||
|
<dc:title>CC-BY-SA</dc:title>
|
||||||
|
</cc:Agent>
|
||||||
|
</dc:rights>
|
||||||
|
<dc:language>FR</dc:language>
|
||||||
|
<cc:license
|
||||||
|
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
|
||||||
|
</cc:Work>
|
||||||
|
<cc:License
|
||||||
|
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
||||||
|
<cc:permits
|
||||||
|
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||||
|
<cc:permits
|
||||||
|
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||||
|
<cc:requires
|
||||||
|
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||||
|
<cc:requires
|
||||||
|
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||||
|
<cc:permits
|
||||||
|
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||||
|
<cc:requires
|
||||||
|
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||||
|
</cc:License>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 6.7 KiB |
BIN
content/articles/2024/bash_printf/images/responsive.png
Normal file
BIN
content/articles/2024/bash_printf/images/responsive.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
BIN
content/articles/2024/bash_printf/images/responsive_corruped.png
Normal file
BIN
content/articles/2024/bash_printf/images/responsive_corruped.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
BIN
content/articles/2024/bash_printf/images/video.gif
Normal file
BIN
content/articles/2024/bash_printf/images/video.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
618
content/articles/2024/bash_printf/index.md
Normal file
618
content/articles/2024/bash_printf/index.md
Normal file
|
@ -0,0 +1,618 @@
|
||||||
|
Title: Bash avancé: barre de progression
|
||||||
|
Category: sysadmin
|
||||||
|
Tags: bash, script, printf, substitution
|
||||||
|
Date: 2024-06-20 0:04
|
||||||
|
Cover: assets/backgrounds/turris_ssh_cle.jpg
|
||||||
|
|
||||||
|
Après [les
|
||||||
|
messages]({filename}../../2022/bash_gerer_les_messages_avance/index.md) et [les
|
||||||
|
signaux]({filename}../../2022/bash_les_pieges/index.md), voici enfin un nouvel
|
||||||
|
article dans la série **Bash avancé**. Avec presque deux ans de retard, il
|
||||||
|
serait temps me direz-vous! Mais mieux vaut tard que jamais non?
|
||||||
|
|
||||||
|
Cette article me servira de prétexte pour utiliser massivement la commande
|
||||||
|
interne `printf` et vous montrer quelques cas d'usages. Nous verrons aussi
|
||||||
|
la *substitution de processus*, la *substitution de paramètre* et d'autres
|
||||||
|
mécanismes offerts par *Bash*.
|
||||||
|
|
||||||
|
## Un peu de théorie
|
||||||
|
|
||||||
|
Il faut d'abord définir ce que j'entends par *barre de progression*: un élément
|
||||||
|
affiché dans notre terminal représentant **la progression** d'une tâche exécutée
|
||||||
|
par un script. Voici un exemple:
|
||||||
|
|
||||||
|
```
|
||||||
|
Task 2/10 ████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ 20%
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette barre se compose donc:
|
||||||
|
|
||||||
|
* d'une légende / titre;
|
||||||
|
* de la barre de progression représentant graphiquement l'avancée de notre
|
||||||
|
tâche;
|
||||||
|
* de la progression en pourcentage.
|
||||||
|
|
||||||
|
### Découpage en segments
|
||||||
|
|
||||||
|
Afin de préparer notre implémentation, découpons notre barre de chargement en
|
||||||
|
segments. Ce découpage nous permettra de faciliter le passage au code.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Voici la légende:
|
||||||
|
|
||||||
|
1. segment d'informations pour la légende;
|
||||||
|
2. segment d'actions effectuées;
|
||||||
|
3. segment d'actions en attentes;
|
||||||
|
4. segment complémentaire qui peut être le pourcentage de progression, ou toute
|
||||||
|
autre information annexe.
|
||||||
|
|
||||||
|
## Une commande comme base
|
||||||
|
|
||||||
|
Pour concevoir notre barre de progression il nous faut analyser la sortie d'une
|
||||||
|
commande. Pour notre tutoriel, je vous propose d'utiliser la commande `ping`.
|
||||||
|
|
||||||
|
```
|
||||||
|
ping -c4 aquilenet.fr
|
||||||
|
PING aquilenet.fr (185.233.100.8) 56(84) bytes of data.
|
||||||
|
64 bytes from hestia.aquilenet.fr (185.233.100.8): icmp_seq=1 ttl=60 time=29.1 ms
|
||||||
|
64 bytes from hestia.aquilenet.fr (185.233.100.8): icmp_seq=2 ttl=60 time=56.3 ms
|
||||||
|
64 bytes from hestia.aquilenet.fr (185.233.100.8): icmp_seq=3 ttl=60 time=52.8 ms
|
||||||
|
64 bytes from hestia.aquilenet.fr (185.233.100.8): icmp_seq=4 ttl=60 time=29.1 ms
|
||||||
|
|
||||||
|
--- aquilenet.fr ping statistics ---
|
||||||
|
4 packets transmitted, 4 received, 0% packet loss, time 3003ms
|
||||||
|
rtt min/avg/max/mdev = 29.055/41.806/56.258/12.764 ms
|
||||||
|
```
|
||||||
|
|
||||||
|
Ci-dessus, la commande a été lancée en indiquant le nombre de requêtes à envoyer
|
||||||
|
via le paramètre `-c4` et chacune de ces requêtes affiche le numéro d'ordre
|
||||||
|
(`icmp_seq=`).
|
||||||
|
|
||||||
|
Maintenant que nous savons où aller, il est temps de commencer à écrire notre
|
||||||
|
script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{! content/articles/2024/bash_printf/files/script1_ping.sh !}
|
||||||
|
```
|
||||||
|
|
||||||
|
[Télécharger le script]({attach}./files/script1_ping.sh)
|
||||||
|
|
||||||
|
Il se compose d'une fonction `command` qui nécessite deux paramètres : un hôte
|
||||||
|
et un nombre d'itérations. Attardons-nous un instant sur deux points importants.
|
||||||
|
|
||||||
|
* Dans la mesure du possible il faut utiliser des variables locales dans les
|
||||||
|
fonctions et en lecture seule pour s'arrurer que la variable ne sera pas
|
||||||
|
modifiée. Exemple: `local -r mavariable` déclare la variable locale
|
||||||
|
`mavariable` en lecture seule avec le paramètre `-r`.
|
||||||
|
* La commande et ses arguments sont enregistrés dans un tableau. Avec cette
|
||||||
|
méthodem plus besoin de gérer avec le gobbing et l'interprétation des
|
||||||
|
paramètres lors de son appel.
|
||||||
|
|
||||||
|
Lors de son lancement, nous pouvons observer l'affichage des différentes
|
||||||
|
requêtes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
./bash script1_ping.sh
|
||||||
|
PING aquilenet.fr (185.233.100.8) 56(84) bytes of data.
|
||||||
|
64 bytes from hestia.aquilenet.fr (185.233.100.8): icmp_seq=1 ttl=60 time=29.1 ms
|
||||||
|
64 bytes from hestia.aquilenet.fr (185.233.100.8): icmp_seq=2 ttl=60 time=56.3 ms
|
||||||
|
# [...]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Capturer les messages de sortie
|
||||||
|
|
||||||
|
Il faut maintenant capturer la sortie de la commande ping et en extraire
|
||||||
|
l'information pertinente : **le numéro de séquence**. [Télécharger le script
|
||||||
|
complet]({attach}./files/script2_getoutput.sh)
|
||||||
|
|
||||||
|
### Récupérer la sortie standard ...
|
||||||
|
|
||||||
|
Il est tout à fait possible de rediriger la sortie de la fonction `command` vers
|
||||||
|
une autre fonction qui traitera le flux de données. C'est exactement le même
|
||||||
|
principe utilisé par l'enchaînement de commande avec un *pipe* comme dans
|
||||||
|
`dmesg | grep 'failure'`. Plusieurs techniques existent pour faire ceci en
|
||||||
|
*Bash*, ici nous allons utiliser la **substitution de processus**.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{! content/articles/2024/bash_printf/files/script2_getoutput.sh!lines=13-21 }
|
||||||
|
```
|
||||||
|
|
||||||
|
Ici la sortie standard de notre fonction `command` est envoyée vers la fonction
|
||||||
|
`parse_output`.
|
||||||
|
|
||||||
|
Notez que cette substitution peut aussi se faire dans l'autre sens avec la
|
||||||
|
notation `<(commande)`, la sortie standard de la commande substituée sera
|
||||||
|
envoyée vers l'entrée de la commande à gauche comme par exemple:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Affiche les éléments trouvés par la commande `ls` en les préfixants de
|
||||||
|
# 'éléments: ', la sortie de notre substitution (ls) sera traitée par la boucle
|
||||||
|
while read -r line; do
|
||||||
|
printf "élément: %s\n" "$line"
|
||||||
|
done < <(ls)
|
||||||
|
```
|
||||||
|
|
||||||
|
Sous le capot, le flux entre la sortie standard de `command` et l'entrée de
|
||||||
|
`parse_output` se fait grâce à un descripteur de fichier.
|
||||||
|
|
||||||
|
### ... et la traiter
|
||||||
|
|
||||||
|
Pour traiter le flux, `parse_output` place chaque ligne qui arrive dans la
|
||||||
|
variable `$line` grâce à la bocle `while` couplée à la commande `read`.
|
||||||
|
|
||||||
|
Ensuite `$line` est affichée en y ajoutant `out:`. Voici ce que donne
|
||||||
|
l'exécution de ce script:
|
||||||
|
|
||||||
|
```text
|
||||||
|
bash script2_getoutput.sh
|
||||||
|
out: PING aquilenet.fr (2a0c:e300::8) 56 data bytes
|
||||||
|
out: 64 bytes from hestia.aquilenet.fr (2a0c:e300::8): icmp_seq=1 ttl=55 time=20.0 ms
|
||||||
|
out: 64 bytes from hestia.aquilenet.fr (2a0c:e300::8): icmp_seq=2 ttl=55 time=19.8 ms
|
||||||
|
[...]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Récupérer le numéro de séquence
|
||||||
|
|
||||||
|
Maintenant que le flux arrive dans `parse_output`, essayons de récupérer le
|
||||||
|
numéro de séquence. Nous pourrons alors déterminer l'état d'avancement de notre
|
||||||
|
commande. [Télécharger le script complet]({attach}./files/script3_rematch.sh)
|
||||||
|
|
||||||
|
Pour l'extraire du reste de la ligne, utilisons l'opérateur de comparaison `=~`
|
||||||
|
qui permet de vérifier la présence d'un motif dans une chaîne de caractères via
|
||||||
|
**une expression rationnelle** comme par exemple:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
if [[ "$nom" =~ ^(Y|y)orick$ ]]; then
|
||||||
|
printf "oui c'est moi!\n"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Il est possible de récupérer le motif capturé entre parenthèses ayant "matché"
|
||||||
|
grâce à la variable `$BASH_REMATCH`, utilisée dans `parse_output`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{! content/articles/2024/bash_printf/files/script3_rematch.sh!lines=13-22 }
|
||||||
|
```
|
||||||
|
|
||||||
|
En plus du numéro de séquence, le temps est aussi capturé afin d'illustrer le
|
||||||
|
fonctionnement de la variable `$BASH_REMATCH` qui est en fait **un tableau**:
|
||||||
|
|
||||||
|
* l'indice `0` permet de récupérer entièrement la chaîne qui correspond au
|
||||||
|
motif;
|
||||||
|
* l'indice `1` au premier élément capturé, ici le numéro de séquence qui se
|
||||||
|
trouve après `icmp_seq=`
|
||||||
|
* et l'indice `2` à tout ce qui se trouve après `time=`.
|
||||||
|
|
||||||
|
Voici ce que donne l'exécution de ce script:
|
||||||
|
|
||||||
|
```text
|
||||||
|
bash script3_rematch.sh
|
||||||
|
séquence: 2 sur 16 temps:20.2 ms
|
||||||
|
séquence: 3 sur 16 temps:20.5 ms
|
||||||
|
séquence: 4 sur 16 temps:19.7 ms
|
||||||
|
[...]
|
||||||
|
```
|
||||||
|
|
||||||
|
Nous avons maintenant tout les éléments utiles pour la construction de notre
|
||||||
|
barre de progression.
|
||||||
|
|
||||||
|
## Première barre de chargement
|
||||||
|
|
||||||
|
Il faut commencer doucement, par une barre de chargement aussi simple que
|
||||||
|
possible, sans fioriture. Nous allons ajouté la fonction `draw_progressbar` qui
|
||||||
|
se charge du dessin à l'écran. [Télécharger le script
|
||||||
|
complet]({attach}./files/script4_progress.sh)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{! content/articles/2024/bash_printf/files/script4_progress.sh!lines=14-26 }
|
||||||
|
```
|
||||||
|
|
||||||
|
`draw_progressbar` attend deux paramètres: le nombre total d'éléments et celui
|
||||||
|
en cours. Cette fonction est appelée depuis `parse_output` et elle utilise
|
||||||
|
`printf` pour dessiner les deux segments nécessaires (mais pas que...).
|
||||||
|
|
||||||
|
### `printf` vers une variable
|
||||||
|
|
||||||
|
Ne nous attardons pas pas sur les deux premières variables locales de notre
|
||||||
|
fonction `draw_progressbar`. Les deux suivantes -- `progress_segment` et
|
||||||
|
`todo_segment` sont par contre plus intéressantes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{! content/articles/2024/bash_printf/files/script4_progress.sh!lines=42 17-21 42 }
|
||||||
|
```
|
||||||
|
|
||||||
|
`printf -v progress_segment` permet **d'affecter le résultat de la commande
|
||||||
|
`printf`** à la variable locale `progress_segment` au lieu de l'afficher sur la
|
||||||
|
sortie standard.
|
||||||
|
|
||||||
|
#### `printf`: format, arguments, spécification
|
||||||
|
|
||||||
|
Le premier argument d'une commande `printf` est le *format*, les suivants
|
||||||
|
sont les *paramètres*. Les arguments sont affichés par le format par
|
||||||
|
l'intermédiaire de *spécifications*.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
number=2
|
||||||
|
drink="water"
|
||||||
|
printf "I have %d bottle of %s.\n" "$number" "$drink"
|
||||||
|
```
|
||||||
|
|
||||||
|
Dans l'exemple suivant:
|
||||||
|
|
||||||
|
* `"I have %d bottle of %s.\n"` est le format,
|
||||||
|
* `$number` et `$drink` sont les arguments
|
||||||
|
* `%d` et `%s` sont des spécifications:
|
||||||
|
* `%d` indique que le premier argument doit être affiché comme
|
||||||
|
un nombre entier;
|
||||||
|
* `%s` indique que le second argument doit être affiché comme une chaîne
|
||||||
|
de caractère.
|
||||||
|
|
||||||
|
Il est possible de définit la taille **minimale d'une spécification**, les
|
||||||
|
caractères manquants seront remplacés par des espaces, comme par exemple:
|
||||||
|
|
||||||
|
```text
|
||||||
|
printf "bonjour %10s, bienvenue\n" "monsieur" "madame" "Linus Torvalds"
|
||||||
|
bonjour monsieur, bienvenue
|
||||||
|
bonjour madame, bienvenue
|
||||||
|
bonjour Linus Torvalds, bienvenue
|
||||||
|
```
|
||||||
|
|
||||||
|
Vous remarquerez que la taille de 10 caractères a bien été réservée pour les
|
||||||
|
deux premiers éléments. Le dernier par contre contient plus de 10 caractères: il
|
||||||
|
dépasse de cet espace.
|
||||||
|
|
||||||
|
Vous remarquez aussi une caractéristique particulière de `printf`. Une seule
|
||||||
|
spécification est utilisée pour trois arguments, le format est donc répété trois
|
||||||
|
fois.
|
||||||
|
|
||||||
|
#### Et dans notre script...
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{! content/articles/2024/bash_printf/files/script4_progress.sh!lines=42 20 21 }
|
||||||
|
```
|
||||||
|
|
||||||
|
Dans le script la taille des deux segments est définie:
|
||||||
|
|
||||||
|
* par **la variable** `progress` pour le *segment des actions effectuées*
|
||||||
|
* par le résultat d'une soustraction pour le *segment des actions en attente*.
|
||||||
|
|
||||||
|
*Bash* réalise les expansions avant de passer dans la commande `printf`,
|
||||||
|
pratique n'est ce pas!
|
||||||
|
|
||||||
|
Pour ces deux `printf` le formats a une spécification avec une taille minimale
|
||||||
|
égale à la taille du segment que nous voulons obtenir (car les arguments sont
|
||||||
|
vides). Nous obtenons **deux variables qui contiennent un nombre d'espace égal
|
||||||
|
à la taille des segments représentés**, il ne reste plus qu'à assembler.
|
||||||
|
|
||||||
|
### On passe au dressage
|
||||||
|
|
||||||
|
La commande `printf` suivante sert à afficher effectivement la barre de
|
||||||
|
progression. D'abord elle est redirigée sur `stderr`, cette sortie est utilisée
|
||||||
|
pour les erreurs, mais aussi pour afficher les informations de diagnostic.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{! content/articles/2024/bash_printf/files/script4_progress.sh!lines=42 22-24 }
|
||||||
|
```
|
||||||
|
|
||||||
|
Son format contient deux spécifications de type chaîne de caractère et **un
|
||||||
|
retour chariot**. Ce dernier permet au curseur de revenir au début de la ligne
|
||||||
|
une fois dessinée. Lors du prochain passage dans la fonction `draw_progressbar`:
|
||||||
|
la barre sera alors réécrite, **pas besoin de gérer l'effacement de la ligne**.
|
||||||
|
|
||||||
|
Pour les deux arguments, nous utilisons *l'expansion de paramètre* afin de
|
||||||
|
remplacer les espaces par les caractères choisis pour symboliser les deux
|
||||||
|
segments de notre barre.
|
||||||
|
|
||||||
|
Comment fonctionne le remplacement de motif avec *l'expansion de paramètre*?
|
||||||
|
Voici deux exemples:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
variable="voici une plante, jolie plante non?"
|
||||||
|
|
||||||
|
# remplacer plante par fleur dans `variable`
|
||||||
|
# remplace la première occurrence trouvée
|
||||||
|
echo "${variable/plante/fleur}"
|
||||||
|
# affiche "voici une fleur, joli plante non?"
|
||||||
|
|
||||||
|
#remplace toutes les occurrences
|
||||||
|
echo "${variable//plante/fleur}"
|
||||||
|
# affiche "voici une fleur, joli fleur non?"
|
||||||
|
```
|
||||||
|
|
||||||
|
Les caractères choisis pour remplacer les espaces sont affecté au tableau
|
||||||
|
`PROGRESS_BAR_CHAR` initialisé en tout début de script.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{! content/articles/2024/bash_printf/files/script4_progress.sh!lines=4 }
|
||||||
|
```
|
||||||
|
|
||||||
|
C'est la fonction `parse_output` qui fait appel à `draw_progressbar`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{! content/articles/2024/bash_printf/files/script4_progress.sh!lines=28-34 }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exécution
|
||||||
|
|
||||||
|
Maintenant que vous avez compris comment le script est construit, il est temps
|
||||||
|
de passer à l'exécution:
|
||||||
|
|
||||||
|
```
|
||||||
|
bash script4_progress.sh
|
||||||
|
█████▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
```
|
||||||
|
|
||||||
|
Notre barre de progression fonctionne, mais elle ne prend qu'une petite part de
|
||||||
|
l'écran (les 16 caractères qui correspondent aux nombres de requêtes totales).
|
||||||
|
Mais surtout, **aucune information n'est affichée**.
|
||||||
|
|
||||||
|
## Ajouter des informations
|
||||||
|
|
||||||
|
Elle est bien belle notre barre de progression, mais là on ne sais pas du tout
|
||||||
|
ce que fait le script. L'angle d'attaque: modifier les fonctions `parse_output`
|
||||||
|
et `draw_progressbar` pour ajouter des informations sur la sortie standard.
|
||||||
|
[Télécharger le script complet]({attach}./files/script5_informations.sh)
|
||||||
|
|
||||||
|
### La fonction `parse_output`
|
||||||
|
|
||||||
|
Elle contient 2 conditions de plus qui vérifient la ligne en cours envoyée par
|
||||||
|
`command`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{!content/articles/2024/bash_printf/files/script5_informations.sh!lines=30-46}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Afficher des information en introduction ...
|
||||||
|
|
||||||
|
La première condition ajoutée récupère, dans la sortie de la commande `ping`,
|
||||||
|
la ligne qui commence par `PING` comme dans l'exemple ci-dessous:
|
||||||
|
|
||||||
|
```
|
||||||
|
PING aquilenet.fr (2a0c:e300::8) 56 data bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
De cette ligne est récupérée **le nom d'hôte** et **l'adresse IP associée** afin
|
||||||
|
de l'afficher avant notre barre de progression.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{!content/articles/2024/bash_printf/files/script5_informations.sh!lines=37}
|
||||||
|
```
|
||||||
|
|
||||||
|
L'information utile est capturée et réutilisée via la variable `BASH_REMATCH`.
|
||||||
|
|
||||||
|
#### ... une conclusion ...
|
||||||
|
|
||||||
|
La seconde condition ajoutée permet de récupérer la ligne qui contient le
|
||||||
|
récapitulatif de ce qui a été fait par commande `ping` et de la renvoyer tel
|
||||||
|
quelle sur la sortie standard.
|
||||||
|
|
||||||
|
#### ... et ajouter le segment d'information
|
||||||
|
|
||||||
|
Enfin un paramètre de plus est passé à la fonction `draw_progressbar`: une
|
||||||
|
chaîne de caractère pour le *segment d'information* contenant la dernière mesure
|
||||||
|
de temps retournée par `ping` (histoire d'utiliser `${BASH_REMATCH[2]}`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{!content/articles/2024/bash_printf/files/script5_informations.sh!lines=32-36}
|
||||||
|
```
|
||||||
|
|
||||||
|
### la fonction `draw_progress`
|
||||||
|
|
||||||
|
Le troisième paramètre passé à cette fonction est affecté à la variable
|
||||||
|
`info_segment`. Son contenu est ajouté à l'affichage avant la barre de
|
||||||
|
chargement.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{!content/articles/2024/bash_printf/files/script5_informations.sh!lines=14-28}
|
||||||
|
```
|
||||||
|
|
||||||
|
### lancement de notre script
|
||||||
|
|
||||||
|
Notre barre de progression est toujours là mais avec un peu de contexte.
|
||||||
|
|
||||||
|
```text
|
||||||
|
bash script5_informations.sh
|
||||||
|
Launch ping command to aquilenet.fr (2a0c:e300::8) with 16 iterations
|
||||||
|
Ping in progress (time: 20.7 ms) ███████▒▒▒▒▒▒▒▒▒
|
||||||
|
```
|
||||||
|
|
||||||
|
Et une fois terminé la barre de progression disparait et laisse place au
|
||||||
|
récapitulatif:
|
||||||
|
|
||||||
|
```text
|
||||||
|
bash script5_informations.sh
|
||||||
|
Launch ping command to aquilenet.fr (2a0c:e300::8) with 16 iterations
|
||||||
|
16 packets transmitted, 16 received, 0% packet loss, time 15019ms
|
||||||
|
```
|
||||||
|
|
||||||
|
## Responsive design inside
|
||||||
|
|
||||||
|
Maintenant que l'affichage des informations est en place, attardons nous sur le
|
||||||
|
côté *responsive design* en adaptant la taille de notre barre à la largeur de
|
||||||
|
l'écran.[Télécharger le script complet]({attach}./files/script2_getoutput.sh)
|
||||||
|
|
||||||
|
La première question est bien évidement comment obtenir la largeur de la fenêtre
|
||||||
|
de terminal? Lorque *Bash* est lancé en **mode interactif** cette largeur --
|
||||||
|
exprimée en nombre de colonnes [^colonne] -- est disponible via la variable
|
||||||
|
`$COLUMNS`. Mais dans le cadre d'un script, cette variable n'est pas disponible
|
||||||
|
si vous n'utilisez pas *Bash* comme interpréteur de commande.
|
||||||
|
|
||||||
|
Il est possible d'utiliser la command interne `shopt` pour activer l'option
|
||||||
|
`checkwinsize` mais elle pose deux problèmes:
|
||||||
|
|
||||||
|
1. Je n'ai pas réussi à activer l'option avec la commande
|
||||||
|
`shopt -s checkwinsize` dans mes tests (mais un simple `shopt` rend les
|
||||||
|
variables `COLUMNS` et `LINES` disponible);
|
||||||
|
2. la récupération des ces deux variables ne se fait **qu'après exécution d'une
|
||||||
|
commande externe**, or il y en a peu dans notre script (`ping`), ce qui
|
||||||
|
posera problème un peu plus loin dans cet article.
|
||||||
|
|
||||||
|
[^colonne]: une colonne correspond à la largeur d'un caractère
|
||||||
|
|
||||||
|
la commande `tput`, qui permet de récupérer des informations sur le terminal,
|
||||||
|
fait très bien le job comme vous pouvez le voir dans ce petit bout de code
|
||||||
|
ajouté dans la fonction `main()`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{!content/articles/2024/bash_printf/files/script6_responsive.sh!lines=54-57}
|
||||||
|
```
|
||||||
|
|
||||||
|
### le dessin de la barre de progression
|
||||||
|
|
||||||
|
Maintenant que nous avons le nombre de colonnes, passons au dessin de notre
|
||||||
|
barre de progression. Voici la nouvelle version de `draw_progressbar`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{!content/articles/2024/bash_printf/files/script6_responsive.sh!lines=14-34}
|
||||||
|
```
|
||||||
|
|
||||||
|
Afin de déterminer la place disponible pour la barre de chargement (`$bar_size`)
|
||||||
|
-- qui sera la partie responsive de notre ligne -- il faut soustaire la taille
|
||||||
|
de notre segment d'information (`$info_segment`) à la largeur (`COLUMNS`).
|
||||||
|
|
||||||
|
La progression doit maintenant s'exprimer par le ratio (`$progress_ratio`) entre
|
||||||
|
les éléments traités (`progress`) et le nombre total d'éléments (`total`).
|
||||||
|
|
||||||
|
L'expansion arithmétique de ***Bash* ne prend pas en charge les nombres
|
||||||
|
flottants**, alors ce ratio s'exprime en pourcentage qui sera arrondi à
|
||||||
|
l'entier le plus proche.
|
||||||
|
|
||||||
|
Il suffit ensuite de calculer le nombre de colonnes occupées par le *segment
|
||||||
|
d'actions effectuées* (`$progress_segment_size`) avec les deux valeurs obtenues
|
||||||
|
précédemment (`$bar_size` et `$progress_ratio`).
|
||||||
|
|
||||||
|
Pour obtenir le nombre de colonnes occupées par le *segment d'actions en
|
||||||
|
attentes*. il suffit de soustraire la taille du segment d'actions effectuées
|
||||||
|
(`$progress_segment_size`) à la taille de la barre (`$bar_size`).
|
||||||
|
|
||||||
|
La suite est simple, dans les deux instructions `printf -v` qui permettent
|
||||||
|
d'affecter le bon nombre d'espaces, il suffit d'utiliser `progress_segment_size`
|
||||||
|
et `todo_segment_size` et le tour est joué.
|
||||||
|
|
||||||
|
### Exécution du script
|
||||||
|
|
||||||
|
Il est temps de passer à l'exécution de cette version de notre script:
|
||||||
|
|
||||||
|
```
|
||||||
|
bash script6_responsive.sh
|
||||||
|
Launch ping command to aquilenet.fr (2a0c:e300::8) with 16 iterations
|
||||||
|
Ping in progress (time: 23.1 ms) █████████████████████████████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
```
|
||||||
|
|
||||||
|
La ligne prend maintenant **toute la place disponible**, la barre de progression
|
||||||
|
ne fait plus juste 16 colonnes. Mais un problème subsiste sur cette version.
|
||||||
|
|
||||||
|
Changer la taille de la fenêtre de terminal ne change pas la taille de la barre.
|
||||||
|
Si agrandir la fenêtre ne pose pas trop de problèmes -- la barre ne prend pas
|
||||||
|
toute la place disponible -- la réduction de la taille mène à ceci:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
L'explication est simple: la valeur de `$COLUMNS` n'est pas rafraichie! Il
|
||||||
|
serait tout à fait possible de récupérer le nombre de colonnes au début de la
|
||||||
|
fonction `draw_progress`, mais ce ne serait pas très optimisé pour une
|
||||||
|
fonctionnalité utilisée rarement. **Une meilleure solution existe...** (mais
|
||||||
|
elle n'est pas si simple).
|
||||||
|
|
||||||
|
## SIGWINCH à la rescousse
|
||||||
|
|
||||||
|
Figurez-vous qu'un signal existe pour signifier un changement de taille d'une
|
||||||
|
fenêtre de terminal. Mais l'implémentation dans notre script est plus épineuse
|
||||||
|
qu'il n'y parait et nécessite quelques changements sur des fonctions d'affichage
|
||||||
|
**pour que notre code soit compatible avec le plus d'émulateurs de terminal**
|
||||||
|
possibles. [Télécharger le script complet]({attach}./files/script7_sigwinch.sh)
|
||||||
|
|
||||||
|
D'abord ajoutons la fonction `change_column_size` qui se charge d'appliquer le
|
||||||
|
changement du nombre de colonnes de la fenêtre de terminal.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{!content/articles/2024/bash_printf/files/script7_sigwinch.sh!lines=56-61}
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette fonction se charge:
|
||||||
|
|
||||||
|
1. de remplir la ligne courante sur `stderr` d'espace pour l'effacer;
|
||||||
|
2. ensuite d'effacer les caractères de la ligne entière grâce à la séquence
|
||||||
|
`\033[0K\r` passée à `printf`;
|
||||||
|
3. enfin de récupérer le nouveau nombre de colonnes.
|
||||||
|
|
||||||
|
Elle sera exécutée dès le changement de géométrie de la fenêtre de terminal
|
||||||
|
(c'est ici que `SIGWINCH` intervient). Cependant une petite subtilité se cache
|
||||||
|
dans la gestion des signaux en *Bash*: ils ne sont pas transmis au *sub-shell*
|
||||||
|
et notre fonction `parse_output` est justement exécuté comme tel via la
|
||||||
|
substitution de processus.
|
||||||
|
|
||||||
|
Pour notre la capture de notre signal `SIGWICH`, il est alors préférable d'ajouter la commande `trap` au début de notre fonction `parse_output`. Si vous voulez plus d'information sir les signaux en Bash, je vous coneille la lecture de mon
|
||||||
|
[précédent article]({filename}../../2022/bash_les_pieges/index.md).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{!content/articles/2024/bash_printf/files/script7_sigwinch.sh!lines=37-40}
|
||||||
|
```
|
||||||
|
### Exécution du script
|
||||||
|
|
||||||
|
Notre barre de progression se redimensionne correctement, mais deux problèmes
|
||||||
|
persistent.
|
||||||
|
|
||||||
|
1. Il faut attendre le rafraichissement de la barre de progression pour qu'elle
|
||||||
|
prenne la bonne dimension une fois la fenêtre redimensionnée.
|
||||||
|
2. La diminution de la taille de la fenêtre de terminal crée des lignes "vides"
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Un programme avec une véritable TUI [^TUI] utilise une boucle et des buffers
|
||||||
|
pour gérer spécifiquement le rafraichissement de l'affichage. Dans le cadre d'un
|
||||||
|
script et de cet article, nous garderons ces deux bugs mineurs et nous en
|
||||||
|
resterons là.
|
||||||
|
|
||||||
|
[^TUI]: Terminal User Interface -- interface utilisateur dans le terminal
|
||||||
|
|
||||||
|
## Bonus stage: un script plus aboutit
|
||||||
|
|
||||||
|
Maintenant que tous nous avons une barre de progression fonctionnelle, il est
|
||||||
|
temps de l'améliorer pour la rendre réutilisable et paramétrable -- et de mettre
|
||||||
|
en place le segment manquant. [Télécharger le script complet]({attach}./files/script8_bonus.sh)
|
||||||
|
|
||||||
|
D'abord il est possible d'ajouter des variables globales pour paramétrer les
|
||||||
|
différents segments de la fonction `draw_progressbar`,
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{!content/articles/2024/bash_printf/files/script8_bonus.sh!lines=5-18}
|
||||||
|
```
|
||||||
|
|
||||||
|
Les variables finissant par `_TEMPLATE` servent de *templates* aux segments
|
||||||
|
d'information et complémentaires. Elles contiennent des définitions de
|
||||||
|
**formats** pour `printf` qui sont utilisée dans la fonction d'affichage comme
|
||||||
|
par exemple la mise en gras du segment d'information.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
{!content/articles/2024/bash_printf/files/script8_bonus.sh!lines=28 2 40-49 2 59-63}
|
||||||
|
```
|
||||||
|
|
||||||
|
Dans `draw_progressbar`, les affichages du segment *d'information* et celui
|
||||||
|
*complémentaire* sont conditionnels. Ensuite les formats des différentes
|
||||||
|
commandes `printf` sont aussi donnés par les variables globales définies plus
|
||||||
|
haut.
|
||||||
|
|
||||||
|
Nous avons maintenant une fonction `draw_progressbar` et les variables associées
|
||||||
|
nous permettant de personnaliser son apparence en fonction du contexte.
|
||||||
|
|
||||||
|
## en conclusion
|
||||||
|
|
||||||
|
Au final, voici ce que que donne notre barre de progression:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Nous avons abordé beaucoup de choses dans cet article un peu plus long que
|
||||||
|
d'habitude. Principalement autour de la commande `printf` et ses nombreuses
|
||||||
|
possibilités (mais nous n'avons pas tout vu...).
|
||||||
|
|
||||||
|
J'espère vous avoir montré que par rapport à la commande `echo` habituellement
|
||||||
|
utilisées dans les script, `printf` est réellement bien plus **puissante** et
|
||||||
|
**utile**.
|
||||||
|
|
||||||
|
Merci à [Heuzef][heuzef], Yishan et [RavenRamirez][alois] pour les nombreuse
|
||||||
|
relectures, corrections et conseils.
|
||||||
|
|
||||||
|
[heuzef]:https://heuzef.com/
|
||||||
|
[alois]:https://aloisbouny.fr/
|
Loading…
Add table
Add a link
Reference in a new issue