Skip to content

Instantly share code, notes, and snippets.

@AlexRNL
Created March 16, 2020 14:31
Show Gist options
  • Select an option

  • Save AlexRNL/8c8d6e012cd677cea8a9af4b153b06e2 to your computer and use it in GitHub Desktop.

Select an option

Save AlexRNL/8c8d6e012cd677cea8a9af4b153b06e2 to your computer and use it in GitHub Desktop.
Useful shell scripts
#!/bin/bash
# Script backup-duplicity.sh
# --------------------------
# Perform a backup of the current machine using duplicity and synchronize it to a remote machine
# Abort on unset variables
set -o nounset
## Global variables
# The date & time format for the logs
readonly LOG_DATETIME_FORMAT="%(%FT%T)T"
# Backup root folder
readonly BACKUP_FOLDER="/backups/duplicity"
# The compression format, used in gpg-options
readonly COMPRESSION_FORMAT="bzip2"
# The file to load SSH agent environment
readonly SSH_AGENT_ENV_FILE="${HOME}/.ssh/agent-env.sh"
# rsync command line options
readonly RSYNC_OPT="${RSYNC_OPT:-""} --archive --compress --human-readable --update --info=NAME --ignore-missing-args --delete"
# Script usage
usage () {
printf "Usage: %s [-h] [-v] [-n] -p <PASSPHRASE_FILE> [-s <SECONDARY_COMPUTER>] [-l <FILE>] [-f <FREQ>] [-d <AGE>]\n" "${0##*/}"
printf "\t-h display this message\n"
printf "\t-v dislay more logs (also add more logs to duplicity)\n"
printf "\t-n enable dry-run mode for duplicity and rsync\n"
printf "\t-p <PASSPHRASE_FILE> the file with the passphrase to use to encrypt the backups\n"
printf "\t-s <SECONDARY_COMPUTER> name of the secondary computer (sync backups between them if set)\n"
printf "\t-l <FILE> the file list to backup (see duplicity man for format)\n"
printf "\t-f <FREQ> frequency between full backups, default is '2W' (see duplicity man for format)\n"
printf "\t-d <AGE> age limit for keeping full backups, default is '1M' (see duplicity man for format)\n"
exit 2
}
# Log only if verbose mode is enabled
verbose () {
if [[ ${VERBOSE:-false} == "true" ]] ; then
log "${*}"
fi
}
# Print dated message
log () {
printf "${LOG_DATETIME_FORMAT} %s\\n" -1 "${*}" 1>&2
}
# Parse command line arguments
parse_arguments () {
VERBOSE="false"
DRY_RUN=""
SECONDARY_COMPUTER=""
FILE_LIST=""
FULL_BACKUP_FREQ="2W"
DELETE_OLDER_THAN="2M"
while getopts ':hvnp:s:l:f:d:' arg
do
case ${arg} in
v) VERBOSE="true"
;;
n) DRY_RUN="--dry-run"
;;
p) PASSPHRASE_FILE="${OPTARG}"
;;
s) SECONDARY_COMPUTER="${OPTARG}"
;;
l) FILE_LIST="${OPTARG}"
;;
f) FULL_BACKUP_FREQ="${OPTARG}"
;;
d) DELETE_OLDER_THAN="${OPTARG}"
;;
h) usage
;;
\?) error "Unknown option-${OPTARG}"
usage
;;
-) error "Missing argument for option -${OPTARG}"
usage
;;
*) error "Option -${arg} not implemented"
usage
;;
esac
done
shift $((OPTIND - 1))
if [[ ! -r ${PASSPHRASE_FILE:-""} ]] ; then
log "Passphrase file is mandatory"
usage
fi
readonly VERBOSE DRY_RUN FILE_LIST SECONDARY_COMPUTER FULL_BACKUP_FREQ DELETE_OLDER_THAN
}
# Get the log file to use
get_log_file () {
printf "%s/duplicity.log" "${BACKUP_FOLDER}"
}
# Make a new backup with the first argument as the folder to backup, and the second one as the name for the backup subfolder
make_new_backup () {
local verbose_level="notice"
if [[ ${VERBOSE} == "true" ]] ; then
verbose_level="info"
fi
duplicity --full-if-older-than "${FULL_BACKUP_FREQ}" \
--gpg-options "--compress-algo=${COMPRESSION_FORMAT}" \
--log-file "$(get_log_file)" \
--verbosity "${verbose_level}" ${DRY_RUN} \
${FILE_LIST:+--include-filelist ${FILE_LIST}} \
"${1}" "file://${BACKUP_FOLDER}/${2}"
}
# Clean old backups with the first argument as the backup subfolder
clean_old_backup () {
duplicity remove-older-than "${DELETE_OLDER_THAN}" \
--log-file "$(get_log_file)" \
--force ${DRY_RUN} \
"file://${BACKUP_FOLDER}/${1}"
}
# Synchronize backups between computers with the first argument as the current hostname
sync_backups () {
if [[ -z ${SECONDARY_COMPUTER} ]] ; then
log "Skipping synchronization no secondary computer is set"
return
fi
if [[ ${1} == "${SECONDARY_COMPUTER}" ]] ; then
log "Skipping synchronization on secondary computer"
return
fi
# Load SSH agent environment
if [[ -r ${SSH_AGENT_ENV_FILE} ]] ; then
# shellcheck source=/home/alex/.ssh/agent-env.sh
source "${SSH_AGENT_ENV_FILE}" > /dev/null
verbose "successfully loaded ${SSH_AGENT_ENV_FILE}: pid=${SSH_AGENT_PID} sock=${SSH_AUTH_SOCK}"
else
log "rsync may not work as file ${SSH_AGENT_ENV_FILE} could not be found"
fi
log "Synchronize backup of this machine to ${SECONDARY_COMPUTER}"
# shellcheck disable=SC2086
rsync ${RSYNC_OPT} ${DRY_RUN} "${BACKUP_FOLDER}"/"${1}" "${SECONDARY_COMPUTER}":"${BACKUP_FOLDER}"
log "Synchronize backup of ${SECONDARY_COMPUTER} to this machine"
# shellcheck disable=SC2086
rsync ${RSYNC_OPT} ${DRY_RUN} "${SECONDARY_COMPUTER}":"${BACKUP_FOLDER}"/"${SECONDARY_COMPUTER}" "${BACKUP_FOLDER}"
}
# Script entry point
main () {
parse_arguments "${@}"
mkdir --parents "${BACKUP_FOLDER}"
log "Starting backups using passphrase from ${PASSPHRASE_FILE}"
PASSPHRASE="$(cat "${PASSPHRASE_FILE}")"
export PASSPHRASE
local hostname
hostname="$(hostname)"
make_new_backup "${HOME}" "${hostname}"
log "${hostname}/${HOME} successfully backed-up!"
log "Rotating backups..."
clean_old_backup "${hostname}"
sync_backups "${hostname}"
}
# Call main function
main "${@}"
#!/bin/bash
# Script bash-colors.sh
# ----------------------
# Display all possible colors (bg, fg, format) with their code
# Script usage
usage () {
printf "Usage: %b%s%b [-h] [-a]\n" "${Color_BBlue}" "${0##*/}" "${Color_Reset}"
printf "\t-h display this message\n"
printf "\t-a display colors on all possible backgrounds\n"
exit 2
}
# Parse command line arguments
parse_arguments () {
NOT_ALL_BACKGROUND="true"
while getopts ':ha' arg
do
case ${arg} in
a) NOT_ALL_BACKGROUND="false"
;;
h) usage
;;
\?) error "Unknown option -${OPTARG}"
usage
;;
:) error "Missing argument for option -${OPTARG}"
usage
;;
*) error "Option -${arg} not implemented"
usage
;;
esac
done
shift $((OPTIND - 1))
readonly NOT_ALL_BACKGROUND
}
# Triple loop function that print each color combination
print_colors () {
# Background
for clbg in {40..47} {100..107} 49 ; do
# Foreground
for clfg in {30..37} {90..97} 39 ; do
# Formatting
for attr in 0 1 2 4 5 7 ; do
# Print the result
printf "\e[${attr};${clbg};${clfg}m ^[%s;%s;%sm \e[0m" "${attr}" "${clbg}" "${clfg}"
done
printf '\n'
done
if [[ ${NOT_ALL_BACKGROUND} == "true" ]] ; then
break
fi
done
}
# Entry point of the script
main () {
# shellcheck source=/home/alex/Documents/scripts/common.sh
source common.sh
parse_arguments "${@}"
print_colors
return 0
}
# Calling main function
main "${@}"
# shellcheck disable=SC2148
# common.sh
# ---------
# Common functions and constants definitions used by several scripts
if [[ -n ${_COMMON_SH_:-""} ]] ; then
return
fi
_COMMON_SH_="common.sh loaded"
# Import colors constants
# shellcheck source=/home/alex/Documents/etc/bash_colors
source "${HOME}/Documents/etc/bash_colors"
## Set useful script options unless we are in 'no-script' mode
if [[ ${1:-""} != "--no-script" ]] ; then
# Abort if a command exits with nonzero status
set -o errexit
# Abort on unset variables
set -o nounset
# Pipe command fail if one fails
set -o pipefail
fi
## Logging functions
# The date & time format for the logs
readonly LOG_DATETIME_FORMAT="%(%FT%T)T"
# Lowest log level: only prints if VERBOSE flag is enabled
fine () {
if [[ ${VERBOSE:-false} == "true" ]] ; then
log White "${*}"
fi
}
# Intermediate log level: prints by default (unless SILENT is enabled)
info () {
if [[ ${SILENT:-false} != "true" ]] ; then
log Cyan "${*}"
fi
}
# Higher log level: always prints a colored output
warn () {
log Yellow "${*}"
}
# Highest log level: always prints a colored output
error () {
log Red "${*}"
}
# Generic log function, uses the first argument as the color (name of variable only!), print the remaining argument
# example: `log Red "the ultimate answer is 42"`
log () {
if [[ ${COLORED_LOGS:-false} != "false" ]] ; then
local color="Color_${1}"
local high_color="Color_I${1}"
local time_color=${!color}
local text_color=${!high_color}
local reset_color=${Color_Reset}
fi
shift
printf "${time_color:-}${LOG_DATETIME_FORMAT} ${text_color:-}%s${reset_color:-}\\n" -1 "${*}" 1>&2
}
## Git functions
readonly WORKSPACE_DIR="${HOME}/workspace"
# Check if the current folder is a Git repo (print an error and returns 1 if it isn't)
is_git_repo () {
if [[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" != "true" ]] ; then
error "Directory ${PWD##*/} is not a Git repository"
return 1
else
fine "Directory ${PWD##*/} is a Git repository"
return 0
fi
}
# List the repositories (sorted) in the workspace and remove build dirs of QtCreator
list_repo () {
find "${WORKSPACE_DIR}" -mindepth 1 -maxdepth 1 ! -type l |
sort |
grep --invert-match --extended-regexp "^${WORKSPACE_DIR}/build-"
}
# Return the name of the current Git branch
get_git_branch () {
printf "%s" "$(git symbolic-ref --short HEAD)"
}
## Function for handling the servers accounts configuration file
# Server to account configuration file
readonly SERVER_ACCOUNT_FILE="${HOME}/Documents/etc/servers-accounts"
# Load server to account configuration
# TODO several accounts per servers?
load_server_account_file () {
# Character indicating a comment line in the configuration file
local comment_prefix='#'
# Separator between server & accounts in the configuration file
local config_file_separator='='
# Declare associative array for the server configuration
declare -gA REMOTE_SERVERS_ACCOUNTS
fine "Load server account file ${SERVER_ACCOUNT_FILE}"
local current_server
local current_account
while IFS=${config_file_separator} read -r current_server current_account; do
# Unless it's a comment, add the line
if [[ -n ${current_server} &&
${current_server:0:${#comment_prefix}} != "${comment_prefix}" ]] ; then
fine "Add account '${current_account}' for server '${current_server}'"
REMOTE_SERVERS_ACCOUNTS[${current_server}]="${current_account}"
fi
done < "${SERVER_ACCOUNT_FILE}"
info "Successfully loaded ${#REMOTE_SERVERS_ACCOUNTS[*]} servers"
export REMOTE_SERVERS_ACCOUNTS
}
# Loads the server account file if needed, check the modification date of the file
load_server_account_file_if_needed () {
local server_conf_modification_date
server_conf_modification_date=$(date -r "${SERVER_ACCOUNT_FILE}" +%s)
if [[ ${server_conf_modification_date} -gt ${SERVER_ACCOUNT_FILE_LAST_RELOAD:-0} ||
-z ${REMOTE_SERVERS_ACCOUNTS+} ]] ; then
load_server_account_file
SERVER_ACCOUNT_FILE_LAST_RELOAD=$(date +%s)
export SERVER_ACCOUNT_FILE_LAST_RELOAD
fi
}
## Miscellaneous functions
# Email recipient
readonly MAIL_RECIPIENT="[email protected]"
# Send summary of operation performed by the script by mail, subject is the first argument
send_mail () {
if [[ ${#} == 0 ]] ; then
error "Need argument to send mail"
return 1
fi
# Extract subject
local subject=${1}
shift
printf "%s" "${*}" |
mail -s "${subject}" \
"${MAIL_RECIPIENT}"
return 0
}
## For testing purposes
# Function that test various functions provided by this file
test_common () {
local mistake="warning"
error "This is an error"
warn "This is a ${mistake}"
info "Public announcement"
fine "This is fine"
log Black "This is not ok" "1 12 123"
log Red "This is not ok" "1 12 123"
log Green "This is not ok" "1 12 123"
log Yellow "This is not ok" "1 12 123"
log Blue "This is not ok" "1 12 123"
log Purple "This is not ok" "1 12 123"
log Cyan "This is not ok" "1 12 123"
log White "This is not ok" "1 12 123"
info "$(list_repo)"
(
cd "${HOME}/workspace/trunk"
if is_git_repo ; then
info "Trunk is a Git repo"
else
error "Trunk SHOULD be a Git repo"
fi
)
(
cd "${HOME}"
if ! is_git_repo 2>/dev/null ; then
info "${HOME} is not a Git repo"
else
error "${HOME} SHOULD NOT be a Git repo"
fi
)
info "Except mail not to be sent, as there is not argument provided"
send_mail || true
local content="Hello, World!
This is a test :-)"
send_mail "this is my subject" "${content}"
}
# If script is called with "TEST_COMMON_SCRIPT" call the test method
if [[ ${1:-} == "TEST_COMMON_SCRIPT" ]] ; then
test_common
fi
#!/bin/bash
# git-check-all.sh
# ----------------
# For all Git repositories in ~/workspace, print a summary of what is NOT pushed remotely
# This script counts locally modified files, stashes and un-pushed commits
# Script usage
usage () {
printf "Usage: %b%s%b [-h] [-v]\n" "${Color_BBlue}" "${0##*/}" "${Color_Reset}"
printf "\t-h display this message\n"
printf "\t-v display more logs\n"
printf "\t-f fetch new refs (branches, tags, etc.) from remotes\n"
exit 2
}
# Parse command line arguments
parse_arguments () {
VERBOSE="false"
FETCH="false"
while getopts ':hvf' arg ; do
case ${arg} in
v) VERBOSE="true"
;;
f) FETCH="true"
;;
h) usage
;;
\?) error "Unknown option -${OPTARG}"
usage
;;
:) error "Missing argument for option -${OPTARG}"
usage
;;
*) error "Option -${arg} not implemented"
usage
;;
esac
done
shift $((OPTIND - 1))
readonly VERBOSE FETCH
}
# Get the number of stash in the repo
get_nb_stash () {
printf "%d" "$(git stash list |
wc --lines)"
}
# Get the number of dirty files in the repo
get_nb_files () {
printf "%d" "$(git status --porcelain |
wc --lines)"
}
# Get the branch difference with the remote, use the branch specified in the 1st argument, or the current branch if not specified
get_diff_branch () {
# Get diff from a different branch if specified
if [[ -n ${1:-""} ]] ; then
if [[ ${1} == "$(get_git_branch)" ]] ; then
return
fi
git checkout --quiet "${1}"
fi
local git_status_branch
git_status_branch=$(git status --porcelain --branch |
grep --extended-regexp "^##")
local regex="^## .+ \[(.+)\]$"
[[ ${git_status_branch} =~ ${regex} ]]
printf "%s" "${BASH_REMATCH[1]:-""}"
# Back to the original branch
if [[ -n ${1:-""} ]] ; then
git checkout --quiet -
fi
}
# Print the summary of the repo, first argument is branch diff, second is nb of modified files and third the number of stash
print_repo_summary () {
local OLD_IFS=${IFS}
IFS="|"
local summary
local idx=0
local log_color=${Color_ICyan}
# Write repo name
printf -v summary[$((idx++))] "For repo %b%s%b[%b%s%b]%b:" "${Color_BBlue}" "${PWD##*/}" "${Color_White}" "${Color_Cyan}" "$(get_git_branch)" "${Color_White}" "${log_color}"
# Write branch diff
if [[ -n ${1} ]] ; then
printf -v summary[$((idx++))] "\t%bcommits%b diff with origin: %b%s%b" "${Color_IGreen}" "${log_color}" "${Color_IYellow}" "${1}" "${log_color}"
fi
# Write master branch diff
if [[ -n ${2} ]] ; then
printf -v summary[$((idx++))] "\t%bcommits%b[%bmaster%b] diff with origin: %b%s%b" "${Color_IGreen}" "${log_color}" "${Color_IRed}" "${log_color}" "${Color_IYellow}" "${2}" "${log_color}"
fi
# Write file diff
if [[ ${3} != 0 ]] ; then
printf -v summary[$((idx++))] "\tmodified %bfiles%b in workspace: %b%d%b" "${Color_IGreen}" "${log_color}" "${Color_IYellow}" "${3}" "${log_color}"
fi
# Write stash number
if [[ ${4} != 0 ]] ; then
printf -v summary[$((idx++))] "\tnumber of %bstash%b: %b%s%b" "${Color_IGreen}" "${log_color}" "${Color_IYellow}" "${4}" "${log_color}"
fi
# Print all lines of summary
for line in ${summary[*]} ; do
info "${line}"
done
IFS=${OLD_IFS}
}
# Print a full status for the current Git repository
check_repo () {
if ! is_git_repo >/dev/null 2>&1 ; then
return 0
fi
if [[ ${FETCH} == "true" ]] ; then
git fetch --quiet 2>/dev/null
if [[ ${?} != 0 ]] ; then
error "Could not fetch refs from remote of repository ${PWD##*/}"
return 1
fi
fi
fine "Processing repository ${PWD##*/}, on branch $(get_git_branch)"
local nb_stash nb_file diff_branch diff_master_branch
nb_stash=$(get_nb_stash)
nb_file=$(get_nb_files)
diff_branch=$(get_diff_branch)
diff_master_branch=$(get_diff_branch "master")
if [[ -z ${diff_branch}
&& -z ${diff_master_branch}
&& ${nb_file} == 0
&& ${nb_stash} == 0 ]] ; then
return 0
fi
print_repo_summary "${diff_branch}" "${diff_master_branch}" "${nb_file}" "${nb_stash}"
return 1
}
# Script entry point
main () {
# shellcheck source=/home/alex/Documents/scripts/common.sh
source common.sh
parse_arguments "${@}"
for dir in $(list_repo) ; do
if ! cd "${dir}" ; then
error "Could not go into '${dir}'"
continue
fi
if ! check_repo ; then
printf "\n"
fi
done
return 0
}
# Call main function
main "${@}"
#!/bin/bash
# Script git-rename.sh
# --------------------
# Script that rename a Git branch, locally and remotely
# Script usage
usage () {
printf "Usage: %b%s%b [-h] [-v] [-n] new-branch-name\n" "${Color_BBlue}" "${0##*/}" "${Color_Reset}"
printf "\t-h display this message\n"
printf "\t-v display more logs\n"
printf "\t-n dry-run, do not rename the branch\n"
exit 2
}
# Parse command line argumments
parse_arguments () {
VERBOSE="false"
DRY_RUN="false"
while getopts ':hvn' arg ; do
case ${arg} in
v) VERBOSE="true"
;;
n) DRY_RUN="true"
;;
h) usage
;;
\?) error "Unknown option -${OPTARG}"
usage
;;
:) error "Missing argument for option -${OPTARG}"
usage
;;
*) error "Option -${arg} not implemented"
usage
;;
esac
done
shift $((OPTIND - 1))
if [[ -z ${1:-""} ]] ; then
error "Please provide a name for the new branch"
usage
fi
NEW_NAME=${1}
readonly VERBOSE DRY_RUN NEW_NAME
}
# Check if the provided argument is a valid Git branch name
is_valid_name () {
if ! git check-ref-format --branch "${1}" >/dev/null 2>&1 ; then
error "${1} is not a valid name for a Git branch"
return 1
fi
return 0
}
# Rename current branch to the first argument
git_rename () {
local old_branch
old_branch=$(git rev-parse --abbrev-ref HEAD)
fine "Renaming branch ${old_branch} to ${1}..."
if [[ ${DRY_RUN} == "false" ]] ; then
if ! (git branch -m "${1}" && \
git push origin :"${old_branch}" "${1}" && \
git push origin --set-upstream "${1}") ; then
warn "Failed to rename branch ${old_branch} to ${1}"
return 1
else
info "Successfully rename branch ${old_branch} to ${1}"
return 0
fi
else
warn "... DRY RUN ... would have renamed ${old_branch} to ${1}"
return 0
fi
}
# Script entry point
main () {
# shellcheck source=/home/alex/Documents/scripts/common.sh
source common.sh
parse_arguments "${@}"
if ! is_git_repo ; then
return 1
fi
if ! is_valid_name "${NEW_NAME}" ; then
return 1
fi
git_rename "${NEW_NAME}"
}
# Call main function
main "${@}"
#!/bin/bash
# git-stl.sh
# ----------
# For current Git repository, list the current modified files by last modification date
# Global variables
# Date display format
readonly DATE_DISPLAY_FORMAT="%Ta %Td %Tb %TY %TH:%TM:%.2TS"
# Script usage
usage () {
printf "Usage: %b%s%b [-h] [-v]\n" "${Color_BBlue}" "${0##*/}" "${Color_Reset}"
printf "\t-h display this message\n"
printf "\t-v display more logs\n"
exit 2
}
# Parse command line arguments
parse_arguments () {
VERBOSE="false"
while getopts ':hv' arg
do
case "${arg}" in
v) VERBOSE="true"
;;
h) usage
;;
\?) error "Unknown option -${OPTARG}"
usage
;;
:) error "Missing argument for option -${OPTARG}"
usage
;;
*) error "Option -${arg} not implemented"
usage
;;
esac
done
shift $((OPTIND - 1))
readonly VERBOSE
}
# Get the list of modified files in the repository
get_modified_files () {
git status --porcelain --untracked-files=all |
grep '^.[?M]' |
sed 's/^.. //'
}
# Get the modified files relative to the current directory
get_modified_files_relative () {
local git_root
git_root=$(git rev-parse --show-toplevel)
for file in $(get_modified_files)
do
realpath --relative-to . "${git_root}/${file}"
done
}
# Print the modified files in the repository sorted by last modified date
print_modified_files () {
local files
files=( $(get_modified_files_relative) )
find "${files[@]}" -printf "%T@\t${DATE_DISPLAY_FORMAT}\t%p\n" |
sort --numeric |
cut --field 2- |
column -t
}
# Script entry point
main () {
# shellcheck source=/home/alex/Documents/scripts/common.sh
source common.sh
parse_arguments "${@}"
if ! is_git_repo ; then
return 1
fi
print_modified_files
return 0
}
# Call main function
main "${@}"
#!/bin/bash
# Script git-tag-rev.sh
# ---------------------
# Script that parses each commit message and verifiy that there are no missing
# tags for commit with message (<llvm-module> v<version>). Propose to add tags
# if there are missing.
# Note that this script is operating in DRY-RUN mode by default (flag '-x' to switch).
# Script usage
usage () {
printf "Usage: %b%s%b [-h] [-v] [-m] [-f] [-a] [-x] [-t]\n" "${Color_BBlue}" "${0##*/}" "${Color_Reset}"
printf "\t-h display this message\n"
printf "\t-v display more logs\n"
printf "\t-q display less logs\n"
printf "\t-m enable multi-repo mode (i.e. in which, a repo has a single module)\n"
printf "\t-f find module in repository history (useful in mono-repo mode i.e. no '-m' option)\n"
printf "\t-a process all reachable commits, not just the 'main branch'\n"
printf "\t-x disable dry run mode %b(will modify current repository)%b\n" "${Color_BYellow}" "${Color_Reset}"
printf "\t-t automatically add tag on found commits\n"
exit 2
}
# Parse command line arguments
parse_arguments () {
VERBOSE="false"
SILENT="false"
MODE="monorepo"
FIND_MODULES="false"
ALL_COMMITS="false"
DRY_RUN="true"
AUTOTAG="false"
while getopts ':hvqmfaxt' arg
do
case ${arg} in
v) VERBOSE="true"
;;
q) SILENT="true"
;;
m) MODE="multi-repo"
;;
f) FIND_MODULES="true"
;;
a) ALL_COMMITS="true"
;;
x) DRY_RUN="false"
;;
t) AUTOTAG="true"
;;
h) usage
;;
\?) error "Unknown option -${OPTARG}"
usage
;;
:) error "Missing argument for option -${OPTARG}"
usage
;;
*) error "Option -${arg} not implemented"
usage
;;
esac
done
shift $((OPTIND - 1))
readonly VERBOSE SILENT MODE FIND_MODULES ALL_COMMITS DRY_RUN AUTOTAG
}
# Function that display the first line of the message (i.e. the "subject") of the commit specified by the SHA passed as a parameter
get_commit_subject () {
git show --no-patch --format=%s "${1}"
}
# Function that display the full message (i.e. the "body") of the commit specified by the SHA passed as a parameter
get_commit_message () {
git show --no-patch --format=%B "${1}"
}
# Function that display the tag (if any) of the commit specified by the SHA passed as a parameter
get_commit_tag () {
local status
# shellcheck disable=SC2034
git describe --tags --exact-match "${1}" 2>/dev/null &&
status=$? ||
status=$?
}
# Fix known mispelling of llvm modules
get_proper_module () {
local module=${1}
case "${module}" in
tf-backtest-llvm | itf-èbacktest-llvm | itf-backtest)
module="itf-backtest-llvm"
;;
itf-pbr-orderrequestion)
module="itf-pbr-orderrequest"
;;
esac
printf "%s" "${module}"
}
# Function that check if the current commit message should be tagged (against a regex) and verify the tag name.
# Tag regex and the commit SHA are expected as parameters.
check_missing_tag () {
local regex subject message tag
regex=${1}
subject=$(get_commit_subject "${2}")
message=$(get_commit_message "${2}")
tag=$(get_commit_tag "${2}")
# Log commit on info if there is a tag
if [[ -z ${tag} ]] ; then
local log_level="fine"
else
local log_level="info"
fi
${log_level} "Current commit ${2:0:8} ${tag:+"with tag '${tag}' and "}message '${subject}'"
# Parse commit subject with regex
if [[ ! ${subject} =~ ${regex} ]] ; then
# The subject does not match the regex => this revision does not require a tag
fine "Skipped because it does not match the regex"
return
fi
local expected_tag
if [[ ${MODE} != "monorepo" ]] ; then
expected_tag=${BASH_REMATCH[1]}
else
# make a matching table of mispell of modules (see ~/tmp/itf-llvm_modules.txt)
# maybe the regex could be more flexible to capture more potential tags?
expected_tag="$(get_proper_module "${BASH_REMATCH[1]}")_${BASH_REMATCH[2]}"
fi
# Check syntax of message
if [[ -z ${tag} || "${tag}" != "${expected_tag}" ]] ; then
# TODO check if tag is already set on an other commit?
warn "Commit ${2:0:8} was expected to be tagged with '${expected_tag}'${tag:+" (found: ${tag}")}"
log White "Commit message:"
log White "${message}"
maybe_tag_commit "${2}" "${expected_tag}"
fi
}
# Function that may tag a commit with the specified tag (SHA and expected tag are passed as parameters)
maybe_tag_commit () {
local sha=${1}
local tag=${2}
# Check if tag is already set
if git rev-parse "${tag}" > /dev/null 2>&1 ; then
error "Tag ${tag} is already used"
read -s -r -n 1 -p "<Press any key to continue>"
return
fi
# If manual mode, propose to tag the commit
if [[ ${AUTOTAG} == "false" ]] ; then
local prompt
printf -v prompt "Tag commit %b%s%b with tag %b%s%b? (y/n) > " "${Color_IBlue}" "${sha:0:8}" "${Color_Reset}" "${Color_IGreen}" "${tag}" "${Color_Reset}"
read -rp "${prompt}" proceed
fi
if [[ ${AUTOTAG} == "true" || ${proceed} == "y" ]] ; then
local tag_command="git tag ${tag} ${sha}"
warn "Tag set with '${tag_command}'"
if [[ ${DRY_RUN} == "false" ]] ; then
${tag_command}
fi
fi
}
# Function that display the SHA of the first parent commit of the SHA passed as a parameter
get_commit_parent () {
git rev-list --parents --max-count 1 "${1}" |
cut --delimiter ' ' --only-delimited --fields 2
}
# Check repo history for missing tags in a repo
check_history () {
# XXX check that current branch is master?
local regex=${1}
local commit_sha='HEAD'
until [[ -z ${commit_sha} ]] ;
do
check_missing_tag "${regex}" "${commit_sha}"
# get the FIRST parent and proceed
commit_sha=$(get_commit_parent ${commit_sha})
done
}
# Check all commit reachable from current HEAD and verify if a tag is missing
check_all_commits () {
local regex=${1}
for sha in $(git log --format="%h") ; do
check_missing_tag "${regex}" "${sha}"
done
}
# Display modules found using the specified regex
find_modules () {
warn "Number of tags per modules:"
git log --oneline |
grep --extended-regexp "${1}" |
sed --regexp-extended --expression "s/^.*${1}.*$/\1/g" |
sort |
uniq --count |
sort --reverse --numeric-sort
}
# Script entry point
main () {
# shellcheck source=/home/alex/Documents/scripts/common.sh
source common.sh
parse_arguments "${@}"
if ! is_git_repo ; then
return 1
fi
if [[ ${MODE} != "monorepo" ]] ; then
local regex="\(${PWD##*/} v([0-9\.]+)\)"
else
local regex="\(([a-zA-Z\-]+).*[v ]([0-9\.]+)\)"
if [[ ${FIND_MODULES} == "true" ]] ; then
find_modules "${regex}"
return 0
fi
fi
if [[ ${ALL_COMMITS} == "true" ]] ; then
check_all_commits "${regex}"
else
check_history "${regex}"
fi
# Propose to push tags?
return 0
}
# Call main function
main "${@}"
#!/bin/bash
# Script next-tram.sh
# -------------------
# Retrieve and parse Ilevia HTML page with the tramway timetable at Cartelot stop
## Global variables
# Command line options for wget
readonly WGET_OPTIONS="--quiet --no-check-certificate"
# Temporary directory
readonly TMP_DIR="/tmp/next-tram"
# File with the whole HTML from Ilevia
readonly ILEVIA_HTML="$TMP_DIR/ilevia.html"
# File with the first direction
readonly DIRECTION1="$TMP_DIR/direction1.html"
# File with the second direction
readonly DIRECTION2="$TMP_DIR/direction2.html"
# Script usage
usage () {
printf "Usage: %b%s%b [-h] [-v] [-d]\n" "${Color_BBlue}" "${0##*/}" "${Color_Reset}"
printf "\t-h display this message\n"
printf "\t-v display more logs\n"
printf "\t-d enable debug mode (e.g. does not delete working files)\n"
printf "\t-s indicate the station (Cartelot or Chateau Rouge)\n"
exit 2
}
# Parse command line arguments
parse_arguments () {
DEBUG="false"
STATION="Cartelot+%%28Wasquehal%%29"
STATION_CODE="2086"
while getopts ':hvds:' arg
do
case ${arg} in
d) DEBUG="true"
;;
s) case "${OPTARG}" in
"Cartelot")
# nothing to do, default value
;;
"Chateau Rouge")
STATION="Chateau+Rouge+%%28Marcq-en-Barœul%%29"
STATION_CODE="1126"
;;
esac
;;
v) VERBOSE="true"
;;
h) usage
;;
\?) error "Unknown option -${OPTARG}"
usage
;;
:) error "Missing argument for option -${OPTARG}"
usage
;;
*) error "Option -${arg} not implemented"
usage
;;
esac
done
shift $((OPTIND - 1))
readonly DEBUG VERBOSE STATION STATION_CODE
}
# Creates the temporary directory for the HTML files
mk_dir () {
mkdir --parents "${TMP_DIR}"
}
# Download the current timetable (in real-time) from the website
download_timetable () {
printf -v txt "Next stops for tram on the %(%d-%m-%Y at %H:%M)T"
info "${txt}"
# Get the page with the results
local url_format="https://nmp-ihm.ctp.prod.canaltp.fr/fr/load/bvbOLvph/schedule/result/?schedule%%5Bstop_area%%5D%%5Bautocomplete%%5D=${STATION}&schedule%%5Bstop_area%%5D%%5Bautocomplete-hidden%%5D=stop_area%%3ATRA%%3ASA%%3A${STATION_CODE}&schedule%%5Bfrom_datetime%%5D%%5Bdate%%5D=%(%d/%m/%Y)T&schedule%%5Bfrom_datetime%%5D%%5Btime%%5D%%5Bhour%%5D=%(%H)T&schedule%%5Bfrom_datetime%%5D%%5Btime%%5D%%5Bminute%%5D=%(%M)T&line=line%%3ATRA%%3A71"
# shellcheck disable=SC2059
printf -v url "${url_format}"
fine "Downloading ${url}"
local wget_status
wget ${WGET_OPTIONS} -O ${ILEVIA_HTML} "${url}" &&
wget_status=$? ||
wget_status=$?
if [[ ${wget_status} -ne 0 ]] ; then
error "wget command exited with code ${wget_status}"
return 1
fi
}
# Split files for each direction
split_files () {
# File reduced to <table> content
local schedule_table_html="${TMP_DIR}/scheduleTable.html"
info "$(grep --only-matching --max-count 1 --extended-regexp 'Horaire à l.*$' ${ILEVIA_HTML} |
sed "s/&#039;/'/g")"
# Timetable are in an HTML <table>
tr "\n\r" "|" < ${ILEVIA_HTML} \
| grep --only-matching --extended-regexp --max-count 1 '<table>.*?Direction.*?</table>' \
| sed --expression 's/|/\n/g' > ${schedule_table_html}
# Split files on 'class="direction-name"' to have one file per direction
local split_line
split_line=$(grep --line-number "class=\"direction-name\"" ${schedule_table_html} \
| cut --delimiter : --fields 1 \
| head --lines 2 \
| tail --lines 1)
fine "Split file ${schedule_table_html} on line ${split_line}"
(head --lines "${split_line}" > ${DIRECTION1}; cat > ${DIRECTION2} ) < ${schedule_table_html}
}
# Print direction and next schedules in each
print_next_tram () {
for file in ${DIRECTION1} ${DIRECTION2} ; do
# Print direction
info "$(grep --extended-regexp --max-count 1 'Gare Lille Flandres' ${file} |
sed --regexp-extended --expression 's/^ +//g ; s/ +$//g')"
# Print next schedules
local next_stops
next_stops=( $(grep --extended-regexp --max-count 2 '([0-9]+ min|[0-9]+h[0-9]+)' ${file} |
sed --regexp-extended --expression 's_<span>([0-9]+ min|[0-9]+h[0-9]+)</span>_\1_g' |
sed --regexp-extended --expression 's/^ +//g ; s/ +$//g') )
info "${next_stops[@]}"
done
}
# Clean-up HTML files
clean_up () {
if [[ ${DEBUG} != "true" ]] ; then
rm --force --recursive ${TMP_DIR:?'Temp dir to delete is not set'}
fi
}
# Script entry point
main () {
# shellcheck source=/home/alex/Documents/scripts/common.sh
source common.sh
parse_arguments "${@}"
mk_dir
download_timetable
split_files
print_next_tram
clean_up
return 0
}
# Call main function
main "${@}"
#!/bin/bash
# Script remote-session.sh
# -----------------------
# Automatically connect to specified remote server using the appriate account
# The server to account configuration is read from a file
## Global variables
# Known accounts to try if server is not configured (TODO read from config file?)
readonly ACCOUNTS=(proorder proinvest devc icingadevc dba alex)
# Script usage
usage () {
printf "Usage: %b%s%b [-h] [-v] [-t] [-a <ACCOUNT>] -s <SERVER>\n" "${Color_BBlue}" "${0##*/}" "${Color_Reset}"
printf "\t-h display this message\n"
printf "\t-v display more logs\n"
printf "\t-q display less logs\n"
printf "\t-t test which accounts are working with the server\n"
printf "\t-a the account to connect to (read from configuration if not specified)\n"
printf "\t-s the server to connect to\n"
exit 2
}
# Parse command line arguments
parse_arguments () {
VERBOSE="false"
SILENT="false"
TEST_ACCOUNTS="false"
while getopts ':hvqta:s:' arg ; do
case ${arg} in
v) VERBOSE="true"
;;
q) SILENT="true"
;;
t) TEST_ACCOUNTS="true"
;;
a) ACCOUNT=${OPTARG}
;;
s) SERVER=${OPTARG}
;;
h) usage
;;
\?) error "Unknown option -${OPTARG}"
usage
;;
:) error "Missing argument for option -${OPTARG}"
usage
;;
*) error "Option -${OPTARG} not implemented"
;;
esac
done
if [[ -z ${SERVER:-""} ]] ; then
error "Missing mandatory -s parameter"
exit 1
fi
readonly VERBOSE TEST_ACCOUNTS ACCOUNT SERVER
}
# Load bashrc_remote on the remote host with ssh
ssh_bashrc () {
local remote_bashrc ssh_status
remote_bashrc=$(base64 --wrap 0 < "${HOME}"/Documents/etc/bashrc_remote)
local remote_bashrc_file="/tmp/${USER}_${$}_bashrc"
# Change title of terminal to display the current session
printf "\033]30;%s\007" "${@}"
# shellcheck disable=SC2029
ssh -t "${@}" "printf \"%s\" \"${remote_bashrc}\" | base64 --decode > ${remote_bashrc_file}; bash --rcfile ${remote_bashrc_file}; rm ${remote_bashrc_file}" &&
ssh_status=$? ||
ssh_status=$?
# Change the title back to the original configuration
printf "\033]30;%%d : %%n\007"
}
# Connect to remote server
connect_to_remote () {
info "Connecting to ${1}@${SERVER}"
ssh_bashrc "${1}"@"${SERVER}"
info "End of remote session with '${SERVER}'"
}
# Try all accounts for the specified server, print all working accounts
get_working_accounts () {
local working_accounts=""
for account in ${ACCOUNTS[*]} ; do
local ssh_status
# Attempt executing a simple command on the remote server
ssh "${account}"@"${SERVER}" "true" 2>/dev/null &&
ssh_status=$? ||
ssh_status=$?
if [[ ${ssh_status} == 0 ]] ; then
info "Can connect to '${SERVER}' with account '${account}'"
working_accounts+="${account} "
else
fine "Cannot connect to '${SERVER}' with account '${account}'"
fi
done
printf "%s" "${working_accounts% *}"
}
# Check if the account(s) (the 1st argument) is empty and returns 1 if that is the case
has_account () {
if [[ -z ${1} ]] ; then
return 1
fi
return 0
}
# Show the list of working account for the specified server and let the user pick the account if several are working
# If the first argument is provided, use it as the working accounts, else call get_working_accounts
# Return 0 and print the selected account on success, if no account found or user selected quit returns 1
select_working_account () {
PS3="Select account to connect to '${SERVER}' # "
local working_accounts
working_accounts=$(get_working_accounts)
# No working account found/provided
if ! has_account "${working_accounts}" ; then
warn "Could not connect to server '${SERVER}' with accounts ${ACCOUNTS[*]}"
return 1
fi
# A single account found/provided
if [[ -z ${working_accounts//[^ ]/} ]] ; then
printf "%s" "${working_accounts}"
return 0
fi
warn "Can connect to '${SERVER}' with accounts '${working_accounts}' ..."
select account in ${working_accounts} "quit"
do
case ${account} in
quit) info "Exiting..."
return 1
;;
*) printf "%s" "${account}"
return 0
;;
esac
done
}
# Get account for remote server with unknown configuration
get_missing_conf_server () {
warn "No predefined accounts found for server '${SERVER}'"
local account
if ! account=$(select_working_account) ; then
return
fi
maybe_add_conf "${account}"
printf "%s" "${account}"
}
# Prompt user if the new configuration should be added to the file
maybe_add_conf () {
local prompt
local existing_conf=${REMOTE_SERVERS_ACCOUNTS[${SERVER}]:-""}
# Check if already present in file, offer to replace it
if [[ -n ${existing_conf} ]] ; then
if [[ ${1} == "${existing_conf}" ]] ; then
# Do nothing, configuration exists
return
fi
printf -v prompt "Replace existing server configuration %b%s%b@%b%s%b (with default account %b%s%b) in configuration file? (y/n) > " "${Color_IRed}" "${existing_conf}" "${Color_Reset}" "${Color_IGreen}" "${SERVER}" "${Color_Reset}" "${Color_ICyan}" "${1}" "${Color_Reset}"
else
printf -v prompt "Add new server configuration %b%s%b (with default account %b%s%b) in configuration file? (y/n) > " "${Color_IGreen}" "${SERVER}" "${Color_Reset}" "${Color_ICyan}" "${1}" "${Color_Reset}"
fi
read -rp "${prompt}" proceed
if [[ ${proceed} == "y" ]] ; then
if [[ -n ${existing_conf} ]] ; then
warn "Replacing configuration for server '${SERVER}' with '${1}' as the default account configuration"
sed --in-place --regexp-extended --expression "s/^${SERVER}=${existing_conf}\$/${SERVER}=${1}/g" "${SERVER_ACCOUNT_FILE}"
else
warn "Adding server '${SERVER}' with '${1}' as a default account to configuration"
printf "%s=%s\n" "${SERVER}" "${1}" >> "${SERVER_ACCOUNT_FILE}"
fi
fi
}
# Entry point of the script
main () {
# shellcheck source=/home/alex/Documents/scripts/common.sh
source common.sh
parse_arguments "${@}"
load_server_account_file_if_needed
local account=${ACCOUNT:-""}
if [[ ${TEST_ACCOUNTS} == "true" ]] ; then
account=$(select_working_account)
maybe_add_conf "${account}"
else
if [[ -z ${account} ]] ; then
# Check if server is in array
account=${REMOTE_SERVERS_ACCOUNTS[${SERVER}]:-""}
fi
if [[ -z ${account} ]] ; then
account=$(get_missing_conf_server)
fi
fi
# Connect to remote if we have an account
if [[ -n ${account} ]] ; then
connect_to_remote "${account}"
return 0
else
return 1
fi
}
# Calling main function
main "${@}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment