mirror of
https://github.com/ivuorinen/dotfiles.git
synced 2026-01-26 03:04:06 +00:00
feat: updates, docs, license fixes, new helpers
This commit is contained in:
@@ -13,7 +13,7 @@ Some problematic code has been fixed per `shellcheck` suggestions.
|
||||
## Sourced
|
||||
|
||||
| Script | Source |
|
||||
| ----------------------- | ----------------- |
|
||||
|-------------------------|-------------------|
|
||||
| `x-dupes` | skx/sysadmin-util |
|
||||
| `x-foreach` | mvdan/dotfiles |
|
||||
| `x-multi-ping` | skx/sysadmin-util |
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
VERBOSE="${VERBOSE:-0}"
|
||||
|
||||
# UTF-8 ftw
|
||||
GITDIRTY="❌ "
|
||||
GITCLEAN="✅ "
|
||||
GIT_DIRTY="❌ "
|
||||
GIT_CLEAN="✅ "
|
||||
|
||||
# Function to print messages if VERBOSE is enabled
|
||||
# $1 - message (string)
|
||||
@@ -41,7 +41,7 @@ catch()
|
||||
|
||||
# Function to check the git status of a directory
|
||||
# $1 - directory (string)
|
||||
gitdirty()
|
||||
git_dirty()
|
||||
{
|
||||
local d="$1"
|
||||
trap 'catch $? $LINENO' ERR
|
||||
@@ -58,15 +58,15 @@ gitdirty()
|
||||
|
||||
# If we have `.git` folder, check it.
|
||||
if [[ -d ".git" ]]; then
|
||||
ISDIRTY=$(git diff --shortstat 2> /dev/null | tail -n1)
|
||||
ICON="$GITCLEAN"
|
||||
GIT_IS_DIRTY=$(git diff --shortstat 2> /dev/null | tail -n1)
|
||||
ICON="$GIT_CLEAN"
|
||||
|
||||
[[ $ISDIRTY != "" ]] && ICON="$GITDIRTY"
|
||||
[[ $GIT_IS_DIRTY != "" ]] && ICON="$GIT_DIRTY"
|
||||
|
||||
printf " %s %s\n" "$ICON" "$(pwd)"
|
||||
else
|
||||
# If it wasn't git repository, check subdirectories.
|
||||
gitdirtyrepos ./*
|
||||
git_dirty_repos ./*
|
||||
fi
|
||||
cd - > /dev/null || exit
|
||||
fi
|
||||
@@ -76,10 +76,10 @@ gitdirty()
|
||||
|
||||
# Function to check git status for multiple directories
|
||||
# $@ - directories
|
||||
gitdirtyrepos()
|
||||
git_dirty_repos()
|
||||
{
|
||||
for x in "$@"; do
|
||||
gitdirty "$x"
|
||||
git_dirty "$x"
|
||||
done
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ main()
|
||||
11) echo "segfault occurred";;
|
||||
esac' EXIT
|
||||
|
||||
gitdirtyrepos "$GIT_DIRTY_DIR"
|
||||
git_dirty_repos "$GIT_DIRTY_DIR"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
153
local/bin/t
153
local/bin/t
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Credit to ThePrimeagen, jessarcher
|
||||
# https://github.com/jessarcher/dotfiles/blob/master/scripts/t
|
||||
# Credit to ThePrimeagen, Jess Archer
|
||||
# See https://github.com/jessarcher/dotfiles/blob/master/scripts/t
|
||||
#
|
||||
# Tweaks by Ismo Vuorinen <https://github.com/ivuorinen> 2025
|
||||
# vim: ft=bash ts=2 sw=2 et
|
||||
@@ -9,104 +9,135 @@
|
||||
# Set environment variables for configuration with defaults
|
||||
T_ROOT="${T_ROOT:-$HOME/Code}"
|
||||
DOTFILES="${DOTFILES:-$HOME/.dotfiles}"
|
||||
T_MAX_DEPTH="${T_MAX_DEPTH:-3}"
|
||||
|
||||
# Function to print an error message and exit
|
||||
error_exit()
|
||||
{
|
||||
error_exit() {
|
||||
echo "Error: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
get_directories()
|
||||
{
|
||||
local dirs=''
|
||||
dirs+='# Directories\n'
|
||||
dirs+=$(
|
||||
find "$T_ROOT" \
|
||||
-maxdepth 3 \
|
||||
-mindepth 1 \
|
||||
-type d \
|
||||
-not -path '*/dist/*' \
|
||||
-not -path '*/dist' \
|
||||
-not -path '*/node_modules/*' \
|
||||
-not -path '*/node_modules' \
|
||||
-not -path '*/vendor/*' \
|
||||
-not -path '*/vendor' \
|
||||
-not -path '*/.idea/*' \
|
||||
-not -path '*/.idea' \
|
||||
-not -path '*/.vscode/*' \
|
||||
-not -path '*/.vscode' \
|
||||
-not -path '*/.git/*' \
|
||||
-not -path '*/.git' \
|
||||
-not -path '*/.svn/*' \
|
||||
-not -path '*/.svn'
|
||||
)
|
||||
dirs+="$(printf "\n%s" "$DOTFILES")"
|
||||
# Validate that T_ROOT exists
|
||||
if [[ ! -d "$T_ROOT" ]]; then
|
||||
error_exit "T_ROOT directory '$T_ROOT' does not exist."
|
||||
fi
|
||||
|
||||
echo "$dirs"
|
||||
|
||||
# Check for required dependencies
|
||||
check_dependencies() {
|
||||
local T_DEPS=(tmux fzf find)
|
||||
for cmd in "${T_DEPS[@]}"; do
|
||||
if ! command -v "$cmd" &> /dev/null; then
|
||||
error_exit "$cmd is not installed."
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_tmux()
|
||||
{
|
||||
check_dependencies
|
||||
|
||||
# Generate an array of '-not -path' rules for each exclusion pattern
|
||||
# without using namerefs.
|
||||
generate_exclude_rules() {
|
||||
local result_var="$1"
|
||||
shift
|
||||
local arr=()
|
||||
for pattern in "$@"; do
|
||||
# Exclude both the directory and any subdirectories under it.
|
||||
arr+=( -not -path "*/${pattern}" -not -path "*/${pattern}/*" )
|
||||
done
|
||||
# Use eval to assign the array to the variable whose name was passed.
|
||||
eval "$result_var=(\"\${arr[@]}\")"
|
||||
}
|
||||
|
||||
get_directories() {
|
||||
local exclude_patterns=(
|
||||
".bzr" ".git" ".hg" ".idea" ".obsidian" ".run" ".svn" ".vscode"
|
||||
"build" "dist" "node_modules" "out" "target" "vendor"
|
||||
)
|
||||
local exclude_rules=()
|
||||
generate_exclude_rules exclude_rules "${exclude_patterns[@]}"
|
||||
|
||||
local dirs
|
||||
# Use $'string' to correctly process escape sequences.
|
||||
dirs=$'# Directories\n'
|
||||
dirs+=$(find "$T_ROOT" \
|
||||
-maxdepth "$T_MAX_DEPTH" \
|
||||
-mindepth 1 \
|
||||
-type d \
|
||||
"${exclude_rules[@]}"
|
||||
)
|
||||
echo -e "$dirs"
|
||||
}
|
||||
|
||||
check_tmux() {
|
||||
if ! command -v tmux &> /dev/null; then
|
||||
error_exit "tmux is not installed."
|
||||
fi
|
||||
|
||||
# check to see that tmux server is running
|
||||
# Ensure tmux server is running
|
||||
if ! tmux info &> /dev/null; then
|
||||
tmux start-server
|
||||
fi
|
||||
}
|
||||
|
||||
get_sessions()
|
||||
{
|
||||
get_sessions() {
|
||||
check_tmux
|
||||
|
||||
local sessions=''
|
||||
sessions+='# Sessions\n'
|
||||
sessions+=$(tmux list-sessions -F "#{session_name}" 2> /dev/null)
|
||||
T_TMUX_SESSIONS=$(tmux list-sessions -F "#{session_name}" 2> /dev/null)
|
||||
|
||||
echo "$sessions"
|
||||
if [[ -z "$T_TMUX_SESSIONS" ]]; then
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "# Sessions\n$T_TMUX_SESSIONS"
|
||||
}
|
||||
|
||||
items=''
|
||||
|
||||
# Select the directory
|
||||
# Determine selection from command-line argument or interactive fzf menu
|
||||
if [[ $# -eq 1 ]]; then
|
||||
selected="$1"
|
||||
else
|
||||
items+=$(get_sessions | sort)
|
||||
items+='\n'
|
||||
items+=$(get_directories | sort)
|
||||
# Combine sessions and directories for selection
|
||||
T_ITEMS="$(get_sessions | sort)
|
||||
$(get_directories | sort)"
|
||||
|
||||
selected=$(echo -e "$items" | fzf) || exit 0 # Exit if no selection is made
|
||||
# Use sort to order the entries and fzf for interactive selection
|
||||
selected=$(echo "$T_ITEMS" | fzf) || exit 0
|
||||
fi
|
||||
|
||||
# If user selected a header, exit
|
||||
[[ ${selected:0:1} == "#" ]] && error_exit "You selected a header, why?"
|
||||
# Reject selection if it is a header line
|
||||
[[ ${selected:0:1} == "#" ]] && error_exit "Header selected. Please choose a valid session or directory."
|
||||
|
||||
# Exit if no directory was selected
|
||||
[[ -z $selected ]] && error_exit "No directory selected."
|
||||
[[ -z "$selected" ]] && error_exit "No directory or session selected."
|
||||
|
||||
# Sanitize the session name
|
||||
session_name=$(basename "$selected")
|
||||
# If we get nothing, we are dealing with a session
|
||||
[[ $session_name == "" ]] && session_name="$selected"
|
||||
# Remove dots from the session name as tmux doesn't like them
|
||||
if [[ -z "$session_name" ]]; then
|
||||
session_name="$selected"
|
||||
fi
|
||||
# Remove dots since tmux dislikes them
|
||||
session_name="${session_name//./}"
|
||||
|
||||
# Try to switch to the tmux session
|
||||
tmux switch-client -t "=$session_name"
|
||||
|
||||
# Attempt to switch to an existing session
|
||||
tmux switch-client -t "=$session_name" 2>/dev/null
|
||||
active_session=$(tmux display-message -p -F '#{session_name}' 2>/dev/null)
|
||||
# echo "active session: $active_session"
|
||||
if [ -n "$active_session" ] && [ "$active_session" == "$session_name" ]; then
|
||||
|
||||
if [[ "$active_session" == "$session_name" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create a new tmux session or attach to an existing one
|
||||
if tmux new-session -c "$selected" -d -s "$session_name" 2> /dev/null; then
|
||||
# Create a new session (or attach to an existing one) based on the selection
|
||||
if [ -z "$TMUX" ]; then
|
||||
# Not inside tmux: create (or attach to) the session and attach.
|
||||
tmux new-session -A -s "$session_name" -c "$selected"
|
||||
else
|
||||
# Inside tmux: check if the target session exists.
|
||||
if tmux has-session -t "$session_name" 2>/dev/null; then
|
||||
# Session exists; switch to it.
|
||||
tmux switch-client -t "$session_name"
|
||||
else
|
||||
tmux new -c "$selected" -A -s "$session_name"
|
||||
# Session does not exist; create it in detached mode and then switch.
|
||||
tmux new-session -d -s "$session_name" -c "$selected"
|
||||
tmux switch-client -t "$session_name"
|
||||
fi
|
||||
fi
|
||||
|
||||
54
local/bin/x-clean-vendordirs
Executable file
54
local/bin/x-clean-vendordirs
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
# vim: ft=bash sw=2 ts=2 et
|
||||
#
|
||||
# Removes vendor and node_modules directories from the
|
||||
# current directory and all subdirectories.
|
||||
#
|
||||
# Author: Ismo Vuorinen 2025
|
||||
# License: MIT
|
||||
|
||||
# Check if the user has provided a directory as an argument
|
||||
if [ "$1" ]; then
|
||||
# Check if the directory exists
|
||||
if [ -d "$1" ]; then
|
||||
CLEANDIR="$1"
|
||||
else
|
||||
msgr err "Directory $1 does not exist."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
CLEANDIR="."
|
||||
fi
|
||||
|
||||
# Function to remove node_modules and vendor folders
|
||||
remove_node_modules_vendor() {
|
||||
local dir=$1
|
||||
|
||||
# If the directory is a symlink, skip it
|
||||
if [ -L "$dir" ]; then
|
||||
msgr msg "Skipping symlink $dir"
|
||||
return
|
||||
fi
|
||||
|
||||
# Check if the directory exists
|
||||
if [ -d "$dir" ]; then
|
||||
# If node_modules or vendor folder exists, remove it and all its contents
|
||||
if [ -d "$dir/node_modules" ]; then
|
||||
msgr run "Removing $dir/node_modules"
|
||||
rm -rf "$dir/node_modules"
|
||||
fi
|
||||
|
||||
if [ -d "$dir/vendor" ]; then
|
||||
msgr run "Removing $dir/vendor"
|
||||
rm -rf "$dir/vendor"
|
||||
fi
|
||||
|
||||
# Recursively check subdirectories
|
||||
for item in "$dir"/*; do
|
||||
remove_node_modules_vendor "$item"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Start removing node_modules and vendor folders from the current working directory
|
||||
remove_node_modules_vendor "$CLEANDIR"
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# foreach <folder> <commands that should be run to each file>
|
||||
# foreach "ls -d */" "git status" # run git status in each folder
|
||||
#
|
||||
# Source: https://github.com/mvdan/dotfiles/blob/master/.bin/foreach
|
||||
|
||||
@@ -11,6 +12,7 @@ for dir in $($cmd); do
|
||||
(
|
||||
echo "$dir"
|
||||
cd "$dir" || exit 1
|
||||
# shellcheck disable=SC2294,SC2034
|
||||
eval "$@" # allow multiple commands like "foo && bar"
|
||||
)
|
||||
done
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Fetch the latest release version of a GitHub repository in tar.gz format (e.g. v1.0.0.tar.gz)
|
||||
# Usage: x-gh-get-latest-release-targ <repo> [--get]
|
||||
# Usage: x-gh-get-latest-release-targz <repo> [--get]
|
||||
# Author: Ismo Vuorinen <https://github.com/ivuorinen> 2024
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -1,66 +1,190 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Get latest release version from GitHub
|
||||
# Get latest release version, branch tag, or latest commit from GitHub
|
||||
# Usage: x-gh-get-latest-version <repo>
|
||||
# Author: Ismo Vuorinen <https://github.com/ivuorinen> 2024
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Enable verbosity with VERBOSE=1
|
||||
# Environment variables, more under get_release_version() and get_latest_branch_tag()
|
||||
# functions. These can be overridden by the user.
|
||||
GITHUB_API_URL="${GITHUB_API_URL:-https://api.github.com/repos}"
|
||||
VERBOSE="${VERBOSE:-0}"
|
||||
|
||||
# Function to print usage information
|
||||
usage()
|
||||
{
|
||||
echo "Usage: $0 <repo> (e.g. ivuorinen/dotfiles)"
|
||||
# Prints a message if VERBOSE=1
|
||||
msg() {
|
||||
[[ "$VERBOSE" -eq 1 ]] && echo "$1"
|
||||
}
|
||||
|
||||
# Show usage information
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 <repo> (e.g. ivuorinen/dotfiles)
|
||||
|
||||
Fetches the latest release version, latest branch tag, or latest commit SHA from GitHub.
|
||||
|
||||
Options:
|
||||
- INCLUDE_PRERELEASES=1 Include prerelease versions (default: only stable releases).
|
||||
- OLDEST_RELEASE=1 Fetch the oldest release instead of the latest.
|
||||
- BRANCH=<branch> Fetch the latest tag from a specific branch (default: main).
|
||||
- LATEST_COMMIT=1 Fetch the latest commit SHA from the specified branch.
|
||||
- OUTPUT=json Return output as JSON (default: plain text).
|
||||
- GITHUB_API_URL=<url> Override GitHub API URL (useful for GitHub Enterprise).
|
||||
- GITHUB_TOKEN=<token> Use GitHub API token to increase rate limits (default: unauthenticated).
|
||||
|
||||
Requirements:
|
||||
- curl
|
||||
- jq (for JSON processing)
|
||||
|
||||
Examples:
|
||||
# Fetch the latest stable release
|
||||
$0 ivuorinen/dotfiles
|
||||
|
||||
# Fetch the latest release including prereleases
|
||||
INCLUDE_PRERELEASES=1 $0 ivuorinen/dotfiles
|
||||
|
||||
# Fetch the oldest release
|
||||
OLDEST_RELEASE=1 $0 ivuorinen/dotfiles
|
||||
|
||||
# Fetch the latest tag from the 'develop' branch
|
||||
BRANCH=develop $0 ivuorinen/dotfiles
|
||||
|
||||
# Fetch the latest commit SHA from 'main' branch
|
||||
LATEST_COMMIT=1 $0 ivuorinen/dotfiles
|
||||
|
||||
# Output result in JSON format
|
||||
OUTPUT=json $0 ivuorinen/dotfiles
|
||||
|
||||
# Use GitHub API token for higher rate limits
|
||||
GITHUB_TOKEN="your_personal_access_token" $0 ivuorinen/dotfiles
|
||||
|
||||
# Use GitHub Enterprise API
|
||||
GITHUB_API_URL="https://github.example.com/api/v3/repos" $0 ivuorinen/dotfiles
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Function to print messages if VERBOSE is enabled
|
||||
# $1 - message (string)
|
||||
msg()
|
||||
{
|
||||
[[ "$VERBOSE" -eq 1 ]] && echo "$1"
|
||||
return 0
|
||||
# Check that required dependencies are installed
|
||||
check_dependencies() {
|
||||
for cmd in curl jq; do
|
||||
if ! command -v "$cmd" &>/dev/null; then
|
||||
echo "Error: '$cmd' is required but not installed." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Function to fetch the latest release version from GitHub
|
||||
# Fetches the latest release or the oldest if OLDEST_RELEASE=1
|
||||
# $1 - GitHub repository (string)
|
||||
get_latest_release()
|
||||
{
|
||||
local repo=$1
|
||||
get_release_version() {
|
||||
local repo="$1"
|
||||
local include_prereleases="${INCLUDE_PRERELEASES:-0}"
|
||||
local oldest_release="${OLDEST_RELEASE:-0}"
|
||||
local api_url="${GITHUB_API_URL}/${repo}/releases"
|
||||
|
||||
local auth_header=()
|
||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
||||
auth_header=(-H "Authorization: token $GITHUB_TOKEN")
|
||||
fi
|
||||
|
||||
msg "Fetching release data from: $api_url (Include prereleases: $include_prereleases, Oldest: $oldest_release)"
|
||||
|
||||
local json_response
|
||||
json_response=$(curl -sSL "${auth_header[@]}" "$api_url")
|
||||
|
||||
# Check for API errors
|
||||
if echo "$json_response" | jq -e 'has("message")' >/dev/null; then
|
||||
msg "GitHub API error: $(echo "$json_response" | jq -r '.message')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local filter='.[] | select(.tag_name)'
|
||||
[[ "$include_prereleases" -eq 0 ]] && filter+='.prerelease == false'
|
||||
|
||||
local version
|
||||
version=$(curl -s "https://api.github.com/repos/${repo}/releases/latest" \
|
||||
| grep "tag_name" \
|
||||
| awk -F '"' '{print $4}')
|
||||
if [[ "$oldest_release" -eq 1 ]]; then
|
||||
version=$(echo "$json_response" | jq -r "[${filter}] | last.tag_name // empty")
|
||||
else
|
||||
version=$(echo "$json_response" | jq -r "[${filter}] | first.tag_name // empty")
|
||||
fi
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
msg "Failed to fetch the latest release version for repository: $repo"
|
||||
echo ""
|
||||
if [[ -z "$version" ]]; then
|
||||
msg "Failed to fetch release version for repository: $repo"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$version"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Fetches the latest tag from the specified branch
|
||||
get_latest_branch_tag() {
|
||||
local repo="$1"
|
||||
local branch="${BRANCH:-main}"
|
||||
local api_url="${GITHUB_API_URL}/${repo}/git/refs/tags"
|
||||
|
||||
msg "Fetching latest tag for branch '$branch' from: $api_url"
|
||||
|
||||
local json_response
|
||||
json_response=$(curl -sSL "$api_url")
|
||||
|
||||
local version
|
||||
version=$(echo "$json_response" | jq -r "[.[] | select(.ref | contains(\"refs/tags/$branch\"))] | last.ref | sub(\"refs/tags/\"; \"\") // empty")
|
||||
|
||||
if [[ -z "$version" ]]; then
|
||||
msg "Failed to fetch latest tag for branch: $branch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$version"
|
||||
}
|
||||
|
||||
# Fetches the latest commit SHA from the specified branch
|
||||
get_latest_commit() {
|
||||
local repo="$1"
|
||||
local branch="${BRANCH:-main}"
|
||||
local api_url="${GITHUB_API_URL}/${repo}/commits/$branch"
|
||||
|
||||
msg "Fetching latest commit SHA from: $api_url"
|
||||
|
||||
local json_response
|
||||
json_response=$(curl -sSL "$api_url")
|
||||
|
||||
local sha
|
||||
sha=$(echo "$json_response" | jq -r '.sha // empty')
|
||||
|
||||
if [[ -z "$sha" ]]; then
|
||||
msg "Failed to fetch latest commit SHA for branch: $branch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$sha"
|
||||
}
|
||||
|
||||
# Main function
|
||||
main()
|
||||
{
|
||||
if [ "$#" -ne 1 ]; then
|
||||
# $1 - GitHub repository (string)
|
||||
main() {
|
||||
if [[ $# -ne 1 ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
local repo=$1
|
||||
check_dependencies
|
||||
|
||||
msg "Fetching the latest release version for repository: $repo"
|
||||
local repo="$1"
|
||||
local result
|
||||
|
||||
local version
|
||||
version=$(get_latest_release "$repo")
|
||||
if [[ "${LATEST_COMMIT:-0}" -eq 1 ]]; then
|
||||
result=$(get_latest_commit "$repo")
|
||||
elif [[ -n "${BRANCH:-}" ]]; then
|
||||
result=$(get_latest_branch_tag "$repo")
|
||||
else
|
||||
result=$(get_release_version "$repo")
|
||||
fi
|
||||
|
||||
echo "$version"
|
||||
return 0
|
||||
if [[ "${OUTPUT:-text}" == "json" ]]; then
|
||||
echo "{\"repository\": \"$repo\", \"result\": \"$result\"}"
|
||||
else
|
||||
echo "$result"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
# License: MIT
|
||||
|
||||
VERSION="1.0.0"
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
|
||||
# Function to display usage
|
||||
usage()
|
||||
{
|
||||
echo "Usage: x-localip [options] [interface]"
|
||||
echo "Usage: $SCRIPT_NAME [options] [interface]"
|
||||
echo "Options:"
|
||||
echo " --help Show this help message"
|
||||
echo " --version Show version information"
|
||||
@@ -31,7 +32,7 @@ while [[ $# -gt 0 ]]; do
|
||||
exit 0
|
||||
;;
|
||||
--version)
|
||||
echo "x-localip version $VERSION"
|
||||
echo "$SCRIPT_NAME version $VERSION"
|
||||
exit 0
|
||||
;;
|
||||
--ipv4)
|
||||
|
||||
183
local/bin/x-multi-ping
Executable file
183
local/bin/x-multi-ping
Executable file
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env bash
|
||||
# x-multi-ping: Multi-protocol ping wrapper in Bash
|
||||
#
|
||||
# Description:
|
||||
# This script pings a list of hostnames using both IPv4 and IPv6 protocols.
|
||||
# It uses the 'dig' command to resolve the hostnames and then pings each IP
|
||||
# address found. The script can run once or loop indefinitely with a sleep
|
||||
# interval between iterations.
|
||||
#
|
||||
# This script is based on the original work by Steve Kemp.
|
||||
# Original work Copyright (c) 2014 by Steve Kemp.
|
||||
#
|
||||
# The code in the original repository may be modified and distributed under your choice of:
|
||||
# * The Perl Artistic License (http://dev.perl.org/licenses/artistic.html) or
|
||||
# * The GNU General Public License, version 2 or later (http://www.gnu.org/licenses/gpl2.txt).
|
||||
#
|
||||
# Modifications and enhancements by Ismo Vuorinen on 2025.
|
||||
#
|
||||
# Usage:
|
||||
# x-multi-ping [--loop|--forever] [--sleep=N] hostname1 hostname2 ...
|
||||
#
|
||||
# Options:
|
||||
# --help Display this help message.
|
||||
# --verbose Enable verbose output.
|
||||
# --loop, --forever Loop indefinitely.
|
||||
# --sleep=N Sleep N seconds between iterations (default: 1).
|
||||
#
|
||||
# Examples:
|
||||
# x-multi-ping example.com
|
||||
# x-multi-ping --loop --sleep=5 example.com
|
||||
# x-multi-ping --forever example.com example.org
|
||||
#
|
||||
# Dependencies:
|
||||
# - dig (DNS lookup utility)
|
||||
# - ping (ICMP ping utility)
|
||||
# - ping6 (IPv6 ping utility) or ping -6 (alternative)
|
||||
#
|
||||
|
||||
# Defaults
|
||||
LOOP=0
|
||||
SLEEP=1
|
||||
VERBOSE=0
|
||||
TIMEOUT=5
|
||||
|
||||
usage()
|
||||
{
|
||||
echo "Usage: $0 [--loop|--forever] [--sleep=N] hostname1 hostname2 ..."
|
||||
echo "Options:"
|
||||
echo " --help Display this help message."
|
||||
echo " --verbose Enable verbose output."
|
||||
echo " --loop, --forever Loop indefinitely."
|
||||
echo " --sleep=N Sleep N seconds between iterations (default: 1)."
|
||||
}
|
||||
|
||||
# Parse command-line options
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--verbose)
|
||||
# shellcheck disable=SC2034
|
||||
VERBOSE=1
|
||||
shift
|
||||
;;
|
||||
--loop | --forever)
|
||||
LOOP=1
|
||||
shift
|
||||
;;
|
||||
--sleep=*)
|
||||
SLEEP="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
--sleep)
|
||||
if [[ -n "$2" ]]; then
|
||||
SLEEP="$2"
|
||||
shift 2
|
||||
else
|
||||
echo "Error: --sleep requires a numeric value."
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
--*)
|
||||
echo "Unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check for required hostnames
|
||||
if [[ $# -lt 1 ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Dependency check for dig and ping
|
||||
if ! command -v dig > /dev/null 2>&1; then
|
||||
echo "The required 'dig' command is missing. Aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v ping > /dev/null 2>&1; then
|
||||
echo "The required 'ping' command is missing. Aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine how to invoke IPv6 ping
|
||||
if command -v ping6 > /dev/null 2>&1; then
|
||||
PING6="ping6"
|
||||
elif ping -6 -c1 ::1 > /dev/null 2>&1; then
|
||||
PING6="ping -6"
|
||||
else
|
||||
echo "The required IPv6 ping command (ping6 or ping -6) is missing. Aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to remove any URI scheme and port from the hostname.
|
||||
strip_hostname()
|
||||
{
|
||||
local host="$1"
|
||||
# Remove leading scheme (e.g., http://) if present.
|
||||
if [[ "$host" =~ ^[a-z]+://([^/]+)/? ]]; then
|
||||
host="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
# Remove a port if specified (e.g., example.com:80).
|
||||
if [[ "$host" =~ ^([^:]+):[0-9]+$ ]]; then
|
||||
host="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
echo "$host"
|
||||
}
|
||||
|
||||
# Function to ping a given host based on DNS lookups.
|
||||
pingHost()
|
||||
{
|
||||
local original_host="$1"
|
||||
local host
|
||||
host=$(strip_hostname "$original_host")
|
||||
|
||||
for type in A AAAA; do
|
||||
# Look up the DNS records for the host.
|
||||
ips=$(dig +short "$host" "$type")
|
||||
if [[ -z "$ips" ]]; then
|
||||
echo "WARNING: Failed to resolve $host [$type]"
|
||||
else
|
||||
# For each IP address found, perform the appropriate ping.
|
||||
while IFS= read -r ip; do
|
||||
if [[ "$type" == "A" ]]; then
|
||||
ping_binary="ping"
|
||||
else
|
||||
ping_binary="$PING6"
|
||||
fi
|
||||
|
||||
# Execute ping with one packet and a timeout.
|
||||
$ping_binary -c1 -w"$TIMEOUT" -W"$TIMEOUT" "$host" > /dev/null 2>&1
|
||||
# shellcheck disable=SC2181
|
||||
if [[ $? -eq 0 ]]; then
|
||||
echo "Host $host - $ip - alive"
|
||||
else
|
||||
echo "Host $host - $ip - FAILED"
|
||||
fi
|
||||
done <<< "$ips"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Main loop: run once or forever based on the options.
|
||||
if [[ $LOOP -eq 1 ]]; then
|
||||
while true; do
|
||||
for host in "$@"; do
|
||||
pingHost "$host"
|
||||
done
|
||||
sleep "$SLEEP"
|
||||
done
|
||||
else
|
||||
for host in "$@"; do
|
||||
pingHost "$host"
|
||||
done
|
||||
fi
|
||||
@@ -1,37 +1,141 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# List open (listened) ports, without the crud that
|
||||
# usually comes with `lsof -i`
|
||||
# List open (listened) ports in Markdown or JSON format.
|
||||
#
|
||||
# Modified by: Ismo Vuorinen <https://github.com/ivuorinen> 2020
|
||||
# Modified by: Ismo Vuorinen <https://github.com/ivuorinen> 2020, 2025
|
||||
# Originally from: https://www.commandlinefu.com/commands/view/8951
|
||||
# Original author: https://www.commandlinefu.com/commands/by/wickedcpj
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Function to print the header
|
||||
print_header()
|
||||
FORMAT="markdown"
|
||||
|
||||
# Function to print help message
|
||||
print_help()
|
||||
{
|
||||
echo 'User: Command: PID: Port:'
|
||||
echo '========================================================='
|
||||
cat << EOF
|
||||
Usage: $(basename "$0") [OPTIONS]
|
||||
|
||||
List open (listened) ports in a formatted table (Markdown) or JSON.
|
||||
|
||||
Options:
|
||||
--json Output results in JSON format instead of Markdown
|
||||
--help Show this help message
|
||||
|
||||
Examples:
|
||||
$(basename "$0") # List open ports as a Markdown table
|
||||
$(basename "$0") --json # List open ports in JSON format
|
||||
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Function to list open ports
|
||||
# Function to print the Markdown table header
|
||||
print_header()
|
||||
{
|
||||
echo "| User | Command | PID | Port |"
|
||||
echo "|------------------|----------------------------|----------|---------|"
|
||||
}
|
||||
|
||||
# Function to list open ports using lsof
|
||||
list_open_ports_lsof()
|
||||
{
|
||||
lsof -i -P -n -sTCP:LISTEN +c 0 2> /dev/null | awk '
|
||||
NR > 1 {
|
||||
port = $9
|
||||
sub(/.*:/, "", port) # Extract port number
|
||||
printf "| %-16s | %-26s | %-8s | %-7s |\n", substr($3, 1, 16), substr($1, 1, 26), substr($2, 1, 8), port
|
||||
}
|
||||
' | sort -k3,3n | uniq
|
||||
}
|
||||
|
||||
# Function to list open ports using ss (alternative)
|
||||
list_open_ports_ss()
|
||||
{
|
||||
ss -ltpn 2> /dev/null | awk '
|
||||
NR > 1 {
|
||||
split($5, addr, ":")
|
||||
port = addr[length(addr)]
|
||||
user = $1
|
||||
cmd = $7
|
||||
sub(/users:\(\(/, "", cmd) # Cleanup command
|
||||
sub(/\)\)/, "", cmd)
|
||||
pid = "-"
|
||||
match(cmd, /pid=([0-9]+)/, m)
|
||||
if (m[1] != "") pid = m[1]
|
||||
printf "| %-16s | %-26s | %-8s | %-7s |\n", substr(user, 1, 16), substr(cmd, 1, 26), substr(pid, 1, 8), port
|
||||
}
|
||||
' | sort -k3,3n | uniq
|
||||
}
|
||||
|
||||
# Function to print JSON output
|
||||
list_open_ports_json()
|
||||
{
|
||||
if command -v lsof &> /dev/null; then
|
||||
lsof -i -P -n -sTCP:LISTEN +c 0 2> /dev/null | awk '
|
||||
NR > 1 {
|
||||
port = $9
|
||||
sub(/.*:/, "", port) # Extract port number
|
||||
printf "{\"user\": \"%s\", \"command\": \"%s\", \"pid\": \"%s\", \"port\": \"%s\"},\n", $3, $1, $2, port
|
||||
}
|
||||
' | sort -k3,3n | uniq | sed '$ s/,$//'
|
||||
elif command -v ss &> /dev/null; then
|
||||
ss -ltpn 2> /dev/null | awk '
|
||||
NR > 1 {
|
||||
split($5, addr, ":")
|
||||
port = addr[length(addr)]
|
||||
user = $1
|
||||
cmd = $7
|
||||
sub(/users:\(\(/, "", cmd)
|
||||
sub(/\)\)/, "", cmd)
|
||||
pid = "-"
|
||||
match(cmd, /pid=([0-9]+)/, m)
|
||||
if (m[1] != "") pid = m[1]
|
||||
printf "{\"user\": \"%s\", \"command\": \"%s\", \"pid\": \"%s\", \"port\": \"%s\"},\n", user, cmd, pid, port
|
||||
}
|
||||
' | sort -k3,3n | uniq | sed '$ s/,$//'
|
||||
else
|
||||
echo "[]"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to determine available command
|
||||
list_open_ports()
|
||||
{
|
||||
lsof -i 4 -P -n +c 0 \
|
||||
| grep -i 'listen' \
|
||||
| awk '{print $3, $1, $2, $9}' \
|
||||
| sed 's/ [a-z0-9\.\*]*:/ /' \
|
||||
| sort -k 3 -n \
|
||||
| xargs printf '%-15s %-25s %-8s %-5s\n' \
|
||||
| uniq
|
||||
if [[ "$FORMAT" == "json" ]]; then
|
||||
echo "["
|
||||
list_open_ports_json
|
||||
echo "]"
|
||||
else
|
||||
print_header
|
||||
if command -v lsof &> /dev/null; then
|
||||
list_open_ports_lsof
|
||||
elif command -v ss &> /dev/null; then
|
||||
list_open_ports_ss
|
||||
else
|
||||
echo "**Error:** Neither 'lsof' nor 'ss' is available."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Main function
|
||||
main()
|
||||
{
|
||||
print_header
|
||||
case "${1:-}" in
|
||||
--json)
|
||||
FORMAT="json"
|
||||
;;
|
||||
--help)
|
||||
print_help
|
||||
;;
|
||||
"") ;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
print_help
|
||||
;;
|
||||
esac
|
||||
|
||||
list_open_ports
|
||||
echo ""
|
||||
}
|
||||
|
||||
281
local/bin/x-path
Executable file
281
local/bin/x-path
Executable file
@@ -0,0 +1,281 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# x-path: A unified script to manipulate the PATH variable.
|
||||
#
|
||||
# This script supports four subcommands:
|
||||
# - append (or a): Remove duplicates and append one or more directories.
|
||||
# - prepend (or p): Remove duplicates and prepend one or more directories.
|
||||
# - remove: Remove one or more directories from PATH.
|
||||
# - check: Check if the directories (or all directories in PATH if none provided) are valid.
|
||||
#
|
||||
# All directory arguments are normalized (trailing slashes removed, except for "/"),
|
||||
# and the current PATH is normalized before any operations.
|
||||
#
|
||||
# Usage:
|
||||
# x-path <command> <directory1> [<directory2> ...]
|
||||
#
|
||||
# Examples:
|
||||
# x-path append /usr/local/bin /opt/bin
|
||||
# x-path p /home/user/bin
|
||||
# x-path remove /usr/local/bin
|
||||
# x-path check # Check all directories in PATH
|
||||
# x-path check /usr/local/bin /bin
|
||||
#
|
||||
# Enable verbose output by setting:
|
||||
# export VERBOSE=1
|
||||
|
||||
VERBOSE="${VERBOSE:-0}"
|
||||
|
||||
#######################################
|
||||
# Normalize a directory by removing a trailing slash (unless the directory is "/").
|
||||
# Globals:
|
||||
# None
|
||||
# Arguments:
|
||||
# $1 - Directory path to normalize
|
||||
# Returns:
|
||||
# Echoes the normalized directory.
|
||||
#######################################
|
||||
normalize_dir()
|
||||
{
|
||||
local d="$1"
|
||||
if [ "$d" != "/" ]; then
|
||||
d="${d%/}"
|
||||
fi
|
||||
echo "$d"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Normalize the PATH variable by normalizing each of its components.
|
||||
# Globals:
|
||||
# PATH
|
||||
# Arguments:
|
||||
# None
|
||||
# Returns:
|
||||
# Updates and exports PATH.
|
||||
#######################################
|
||||
normalize_path_var()
|
||||
{
|
||||
local new_path=""
|
||||
local d
|
||||
IFS=':' read -r -a arr <<< "$PATH"
|
||||
for d in "${arr[@]}"; do
|
||||
d=$(normalize_dir "$d")
|
||||
if [ -z "$new_path" ]; then
|
||||
new_path="$d"
|
||||
else
|
||||
new_path="$new_path:$d"
|
||||
fi
|
||||
done
|
||||
PATH="$new_path"
|
||||
export PATH
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Remove all occurrences of a normalized directory from PATH.
|
||||
# Globals:
|
||||
# PATH
|
||||
# Arguments:
|
||||
# $1 - Normalized directory to remove from PATH.
|
||||
# Returns:
|
||||
# Updates PATH.
|
||||
#######################################
|
||||
remove_from_path()
|
||||
{
|
||||
local d="$1"
|
||||
PATH=":${PATH}:"
|
||||
PATH="${PATH//:$d:/:}"
|
||||
PATH="${PATH#:}"
|
||||
PATH="${PATH%:}"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Append one or more directories to PATH.
|
||||
# Globals:
|
||||
# PATH, VERBOSE
|
||||
# Arguments:
|
||||
# One or more directory paths.
|
||||
# Returns:
|
||||
# Updates PATH.
|
||||
#######################################
|
||||
do_append()
|
||||
{
|
||||
local processed=""
|
||||
local d
|
||||
for arg in "$@"; do
|
||||
d=$(normalize_dir "$arg")
|
||||
if [[ " $processed " == *" $d "* ]]; then
|
||||
continue
|
||||
else
|
||||
processed="$processed $d"
|
||||
fi
|
||||
|
||||
if [ ! -d "$d" ]; then
|
||||
[ "$VERBOSE" -eq 1 ] && echo "(?) Directory '$d' does not exist. Skipping."
|
||||
continue
|
||||
fi
|
||||
|
||||
remove_from_path "$d"
|
||||
PATH="${PATH:+"$PATH:"}$d"
|
||||
[ "$VERBOSE" -eq 1 ] && echo "Appended '$d' to PATH."
|
||||
done
|
||||
export PATH
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Prepend one or more directories to PATH.
|
||||
# Directories are processed in reverse order so that the first argument ends up leftmost.
|
||||
# Globals:
|
||||
# PATH, VERBOSE
|
||||
# Arguments:
|
||||
# One or more directory paths.
|
||||
# Returns:
|
||||
# Updates PATH.
|
||||
#######################################
|
||||
do_prepend()
|
||||
{
|
||||
local processed=""
|
||||
local d
|
||||
local -a arr=("$@")
|
||||
local i
|
||||
for ((i = ${#arr[@]} - 1; i >= 0; i--)); do
|
||||
d=$(normalize_dir "${arr[i]}")
|
||||
if [[ " $processed " == *" $d "* ]]; then
|
||||
continue
|
||||
else
|
||||
processed="$processed $d"
|
||||
fi
|
||||
|
||||
if [ ! -d "$d" ]; then
|
||||
[ "$VERBOSE" -eq 1 ] && echo "(?) Directory '$d' does not exist. Skipping."
|
||||
continue
|
||||
fi
|
||||
|
||||
remove_from_path "$d"
|
||||
PATH="$d${PATH:+":$PATH"}"
|
||||
[ "$VERBOSE" -eq 1 ] && echo "Prepended '$d' to PATH."
|
||||
done
|
||||
export PATH
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Remove one or more directories from PATH.
|
||||
# Globals:
|
||||
# PATH, VERBOSE
|
||||
# Arguments:
|
||||
# One or more directory paths.
|
||||
# Returns:
|
||||
# Updates PATH.
|
||||
#######################################
|
||||
do_remove()
|
||||
{
|
||||
local processed=""
|
||||
local d
|
||||
for arg in "$@"; do
|
||||
d=$(normalize_dir "$arg")
|
||||
if [[ " $processed " == *" $d "* ]]; then
|
||||
continue
|
||||
else
|
||||
processed="$processed $d"
|
||||
fi
|
||||
|
||||
case ":$PATH:" in
|
||||
*":$d:"*)
|
||||
remove_from_path "$d"
|
||||
[ "$VERBOSE" -eq 1 ] && echo "Removed '$d' from PATH."
|
||||
;;
|
||||
*)
|
||||
[ "$VERBOSE" -eq 1 ] && echo "(?) '$d' is not in PATH."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
export PATH
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Check the validity of directories.
|
||||
# If arguments are provided, check those directories; otherwise, check all directories in PATH.
|
||||
# Globals:
|
||||
# PATH
|
||||
# Arguments:
|
||||
# Zero or more directory paths.
|
||||
# Returns:
|
||||
# Outputs the validity status of each directory.
|
||||
#######################################
|
||||
do_check()
|
||||
{
|
||||
local d
|
||||
if [ "$#" -eq 0 ]; then
|
||||
echo "Checking all directories in PATH:"
|
||||
IFS=':' read -r -a arr <<< "$PATH"
|
||||
for d in "${arr[@]}"; do
|
||||
d=$(normalize_dir "$d")
|
||||
if [ -d "$d" ]; then
|
||||
echo "Valid: $d"
|
||||
else
|
||||
echo "Invalid: $d"
|
||||
fi
|
||||
done
|
||||
else
|
||||
for arg in "$@"; do
|
||||
d=$(normalize_dir "$arg")
|
||||
if [ -d "$d" ]; then
|
||||
echo "Valid: $d"
|
||||
else
|
||||
echo "Invalid: $d"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Main routine: Parse subcommand and arguments, normalize PATH,
|
||||
# and dispatch to the appropriate functionality.
|
||||
#######################################
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "Usage: $0 <command> <directory1> [<directory2> ...]"
|
||||
echo "Commands:"
|
||||
echo " append (or a) - Append directories to PATH"
|
||||
echo " prepend (or p) - Prepend directories to PATH"
|
||||
echo " remove - Remove directories from PATH"
|
||||
echo " check - Check validity of directories (or all in PATH if none given)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cmd="$1"
|
||||
shift
|
||||
|
||||
# Normalize the current PATH variable.
|
||||
normalize_path_var
|
||||
|
||||
case "$cmd" in
|
||||
append | a)
|
||||
[ "$#" -ge 1 ] || {
|
||||
echo "Usage: $0 append <directory1> [<directory2> ...]"
|
||||
exit 1
|
||||
}
|
||||
do_append "$@"
|
||||
;;
|
||||
prepend | p)
|
||||
[ "$#" -ge 1 ] || {
|
||||
echo "Usage: $0 prepend <directory1> [<directory2> ...]"
|
||||
exit 1
|
||||
}
|
||||
do_prepend "$@"
|
||||
;;
|
||||
remove)
|
||||
[ "$#" -ge 1 ] || {
|
||||
echo "Usage: $0 remove <directory1> [<directory2> ...]"
|
||||
exit 1
|
||||
}
|
||||
do_remove "$@"
|
||||
;;
|
||||
check)
|
||||
# If no directories are provided, check all directories in PATH.
|
||||
do_check "$@"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $cmd"
|
||||
echo "Usage: $0 <command> <directory1> [<directory2> ...]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -1,40 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Add a directory to the beginning of the PATH if it's not already there.
|
||||
# Usage: x-path-append <dir>
|
||||
# Optimized script to append directories to PATH.
|
||||
# For each given directory, it removes all duplicate occurrences from PATH
|
||||
# and then appends it if the directory exists.
|
||||
#
|
||||
# Usage: x-path-append <directory1> [<directory2> ...]
|
||||
#
|
||||
# Enable verbose output by setting the environment variable VERBOSE=1.
|
||||
#
|
||||
# Author: Ismo Vuorinen <https://github.com/ivuorinen> 2024
|
||||
# License: MIT
|
||||
|
||||
# Set verbosity with VERBOSE=1
|
||||
VERBOSE="${VERBOSE:-0}"
|
||||
|
||||
# Function to print messages if VERBOSE is enabled
|
||||
# $1 - message (string)
|
||||
msg()
|
||||
{
|
||||
[[ "$VERBOSE" -eq 1 ]] && echo "$1"
|
||||
# Ensure that at least one directory is provided.
|
||||
[ "$#" -lt 1 ] && {
|
||||
echo "Usage: $0 <directory> [<directory> ...]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <dir>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dir="$1"
|
||||
|
||||
if echo "$PATH" | grep -qE "(^|:)$dir($|:)"; then
|
||||
export PATH=$(echo -n "$PATH" | awk -v RS=: -v ORS=: "\$0 != \"$dir\"" | sed 's/:$//')
|
||||
msg "Directory $dir has been removed from PATH"
|
||||
else
|
||||
msg "Directory $dir is not in PATH"
|
||||
fi
|
||||
|
||||
for dir in "$@"; do
|
||||
# Check if the specified directory exists.
|
||||
if [ ! -d "$dir" ]; then
|
||||
msg "(?) Directory $dir does not exist"
|
||||
exit 0
|
||||
[ "$VERBOSE" -eq 1 ] && echo "(?) Directory '$dir' does not exist. Skipping."
|
||||
continue
|
||||
fi
|
||||
|
||||
if echo "$PATH" | grep -qE "(^|:)$dir($|:)"; then
|
||||
msg "(!) Directory $dir is already in PATH"
|
||||
else
|
||||
# Remove all duplicate occurrences of the directory from PATH.
|
||||
case ":$PATH:" in
|
||||
*":$dir:"*)
|
||||
PATH=":${PATH}:"
|
||||
PATH="${PATH//:$dir:/:}"
|
||||
PATH="${PATH#:}"
|
||||
PATH="${PATH%:}"
|
||||
[ "$VERBOSE" -eq 1 ] && echo "Removed previous occurrences of '$dir' from PATH."
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
# Append the directory to PATH.
|
||||
export PATH="${PATH:+"$PATH:"}$dir"
|
||||
msg "(!) Directory $dir has been added to the end of PATH"
|
||||
fi
|
||||
[ "$VERBOSE" -eq 1 ] && echo "Appended '$dir' to PATH."
|
||||
done
|
||||
|
||||
@@ -1,33 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Add a directory to the front of the PATH if it exists and is not already there
|
||||
# Usage: x-path-prepend <dir>
|
||||
# Optimized script to batch prepend directories to PATH.
|
||||
# For each given directory, it removes all duplicate occurrences from PATH
|
||||
# and then prepends it. Directories that do not exist are skipped.
|
||||
#
|
||||
# Usage: x-path-prepend <directory1> [<directory2> ...]
|
||||
#
|
||||
# Enable verbose output by setting the environment variable VERBOSE=1.
|
||||
#
|
||||
# Author: Ismo Vuorinen <https://github.com/ivuorinen> 2024
|
||||
# License: MIT
|
||||
|
||||
# Set verbosity with VERBOSE=1
|
||||
VERBOSE="${VERBOSE:-0}"
|
||||
|
||||
# Function to print messages if VERBOSE is enabled
|
||||
# $1 - message (string)
|
||||
msg()
|
||||
{
|
||||
[[ "$VERBOSE" -eq 1 ]] && echo "$1"
|
||||
# Ensure that at least one argument is provided.
|
||||
[ "$#" -lt 1 ] && {
|
||||
echo "Usage: $0 <directory> [<directory> ...]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <dir>"
|
||||
exit 1
|
||||
fi
|
||||
# Save the arguments in an array.
|
||||
dirs=("$@")
|
||||
|
||||
dir="$1"
|
||||
# Process the directories in reverse order so that the first argument ends up leftmost in PATH.
|
||||
for ((idx = ${#dirs[@]} - 1; idx >= 0; idx--)); do
|
||||
dir="${dirs[idx]}"
|
||||
|
||||
# Check if the specified directory exists.
|
||||
if [ ! -d "$dir" ]; then
|
||||
msg "(?) Directory $dir does not exist"
|
||||
exit 0
|
||||
[ "$VERBOSE" -eq 1 ] && echo "(?) Directory '$dir' does not exist. Skipping."
|
||||
continue
|
||||
fi
|
||||
|
||||
if echo "$PATH" | grep -qE "(^|:)$dir($|:)"; then
|
||||
msg "(!) Directory $dir is already in PATH"
|
||||
else
|
||||
# Remove all duplicate occurrences of the directory from PATH using built-in string operations.
|
||||
case ":$PATH:" in
|
||||
*":$dir:"*)
|
||||
PATH=":${PATH}:"
|
||||
PATH="${PATH//:$dir:/:}"
|
||||
PATH="${PATH#:}"
|
||||
PATH="${PATH%:}"
|
||||
[ "$VERBOSE" -eq 1 ] && echo "Removed duplicate occurrences of '$dir' from PATH."
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
# Prepend the directory to PATH.
|
||||
export PATH="$dir${PATH:+":$PATH"}"
|
||||
msg "(!) Directory $dir has been added to the front of PATH"
|
||||
fi
|
||||
[ "$VERBOSE" -eq 1 ] && echo "Prepended '$dir' to PATH."
|
||||
done
|
||||
|
||||
@@ -1,29 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Remove a directory from the PATH
|
||||
# Usage: x-path-remove <dir>
|
||||
# Optimized script to remove directories from PATH.
|
||||
# For each specified directory, all occurrences are removed from PATH.
|
||||
#
|
||||
# Usage: x-path-remove <directory1> [<directory2> ...]
|
||||
#
|
||||
# Enable verbose output by setting the environment variable VERBOSE=1.
|
||||
#
|
||||
# Author: Ismo Vuorinen <https://github.com/ivuorinen> 2024
|
||||
# License: MIT
|
||||
|
||||
# Set verbosity with VERBOSE=1
|
||||
VERBOSE="${VERBOSE:-0}"
|
||||
|
||||
# Function to print messages if VERBOSE is enabled
|
||||
# $1 - message (string)
|
||||
msg()
|
||||
{
|
||||
[[ "$VERBOSE" -eq 1 ]] && echo "$1"
|
||||
# Ensure that at least one directory is provided.
|
||||
[ "$#" -lt 1 ] && {
|
||||
echo "Usage: $0 <directory> [<directory> ...]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <dir>"
|
||||
exit 1
|
||||
fi
|
||||
for dir in "$@"; do
|
||||
# Remove trailing slash if present, unless the directory is "/"
|
||||
[ "$dir" != "/" ] && dir="${dir%/}"
|
||||
|
||||
dir="$1"
|
||||
# Check if the directory is present in PATH.
|
||||
case ":$PATH:" in
|
||||
*":$dir:"*)
|
||||
# Remove all occurrences of the directory from PATH using parameter expansion.
|
||||
PATH=":${PATH}:"
|
||||
PATH="${PATH//:$dir:/:}"
|
||||
PATH="${PATH#:}"
|
||||
PATH="${PATH%:}"
|
||||
[ "$VERBOSE" -eq 1 ] && echo "Removed '$dir' from PATH."
|
||||
;;
|
||||
*)
|
||||
[ "$VERBOSE" -eq 1 ] && echo "(?) '$dir' is not in PATH."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ! echo "$PATH" | grep -qE "(^|:)$dir($|:)"; then
|
||||
msg "(?) Directory $dir is not in PATH"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
export PATH=$(echo -n "$PATH" | awk -v RS=: -v ORS=: "\$0 != \"$dir\"" | sed 's/:$//')
|
||||
msg "(!) Directory $dir has been removed from PATH"
|
||||
export PATH
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Quota usage
|
||||
* Show quota usage
|
||||
@@ -10,6 +11,7 @@
|
||||
* @license MIT
|
||||
* @author Ismo Vuorinen <https://github.com/ivuorinen>
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
|
||||
$debug = false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# DESCRIPTION:
|
||||
# Simple recording tool and wrapper around giph (ffmpeg).
|
||||
|
||||
@@ -1,71 +1,137 @@
|
||||
#!/usr/bin/env bash
|
||||
# Check which PHP versions are installed with brew, and create aliases for each installation.
|
||||
# Copyright (c) 2023 Ismo Vuorinen. All Rights Reserved.
|
||||
# -----------------------------------------------------------------------------
|
||||
# This script caches the list of PHP installations via Homebrew and generates
|
||||
# shell aliases for each installation. Both the brew list and the generated
|
||||
# alias definitions are stored in the XDG cache directory.
|
||||
#
|
||||
# If the brew list cache is invalid (older than CACHE_TTL), then both caches are
|
||||
# regenerated. Otherwise, if only the alias cache is stale, it is regenerated
|
||||
# from the brew list cache.
|
||||
#
|
||||
# Usage:
|
||||
# source x-set-php-aliases.sh
|
||||
#
|
||||
# (C) 2023, 2025 Ismo Vuorinen. All Rights Reserved.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Set verbosity with VERBOSE=1 x-set-php-aliases
|
||||
# Set verbosity level (0 by default; set to 1 or 2 for more detail)
|
||||
VERBOSE="${VERBOSE:-0}"
|
||||
|
||||
# Enable debugging if verbosity is set to 2
|
||||
[ "$VERBOSE" = "2" ] && set -x
|
||||
|
||||
# Check if brew is installed, if not exit.
|
||||
# Exit early if Homebrew is not installed.
|
||||
if ! command -v brew &> /dev/null; then
|
||||
echo "Homebrew is not installed. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Function to read installed PHP versions using brew
|
||||
get_php_versions()
|
||||
# Determine Homebrew's prefix.
|
||||
HOMEBREW_PREFIX="${HOMEBREW_PREFIX:-$(brew --prefix)}"
|
||||
|
||||
# Determine the XDG cache directory (default to ~/.cache).
|
||||
XDG_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}"
|
||||
CACHE_DIR="${XDG_CACHE}/x-set-php-aliases"
|
||||
mkdir -p "$CACHE_DIR"
|
||||
|
||||
# Define cache file paths.
|
||||
BREW_LIST_CACHE="${CACHE_DIR}/brew_list.cache"
|
||||
ALIASES_CACHE="${CACHE_DIR}/aliases.cache"
|
||||
|
||||
# Cache time-to-live in seconds (here 300 seconds = 5 minutes).
|
||||
CACHE_TTL=300
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Function: cache_is_valid
|
||||
# Returns 0 if the file exists and its modification time is within TTL.
|
||||
# -----------------------------------------------------------------------------
|
||||
cache_is_valid()
|
||||
{
|
||||
local versions=()
|
||||
while IFS="" read -r line; do
|
||||
versions+=("$line")
|
||||
done < <(bkt -- brew list | grep '^php')
|
||||
echo "${versions[@]}"
|
||||
local file="$1"
|
||||
local ttl="$2"
|
||||
if [[ -f "$file" ]]; then
|
||||
local mod_time
|
||||
if stat --version &> /dev/null; then
|
||||
mod_time=$(stat -c %Y "$file")
|
||||
else
|
||||
mod_time=$(stat -f %m "$file")
|
||||
fi
|
||||
local current_time
|
||||
current_time=$(date +%s)
|
||||
if ((current_time - mod_time < ttl)); then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to create aliases for each PHP version
|
||||
create_aliases()
|
||||
# -----------------------------------------------------------------------------
|
||||
# Function: generate_aliases
|
||||
# Reads PHP formulas (one per line) from the specified file and prints out
|
||||
# alias definitions for each valid PHP installation.
|
||||
#
|
||||
# The following aliases are created (assuming the formula is "php@80"):
|
||||
#
|
||||
# p80r : Raw PHP (executable only)
|
||||
# p80 : PHP with an error reporting flag enabled
|
||||
# p80s : Launches a PHP local server at localhost:9000
|
||||
# p80c : Runs composer (if found) using this PHP and error reporting flag
|
||||
# -----------------------------------------------------------------------------
|
||||
generate_aliases()
|
||||
{
|
||||
local php_versions=("$@")
|
||||
local brew_file="$1"
|
||||
local php_error_reporting='-d error_reporting=22527'
|
||||
local composer_path
|
||||
composer_path=$(command -v composer 2> /dev/null || true)
|
||||
|
||||
for version in "${php_versions[@]}"; do
|
||||
[ "$VERBOSE" = "1" ] && echo "Setting aliases for $version"
|
||||
while IFS= read -r version || [[ -n "$version" ]]; do
|
||||
# Remove any leading/trailing whitespace.
|
||||
version=$(echo "$version" | xargs)
|
||||
[[ -z "$version" ]] && continue
|
||||
|
||||
# Drop the dot from version (e.g., 8.0 -> 80)
|
||||
# Compute an alias name: remove dots and replace "php@" with "p"
|
||||
local php_abbr="${version//\./}"
|
||||
# Replace "php@" with "p" so "php@80" becomes "p80"
|
||||
local php_alias="${php_abbr//php@/p}"
|
||||
|
||||
# Fetch the exec path once
|
||||
local php_exec="$HOMEBREW_PREFIX/opt/$version/bin/php"
|
||||
|
||||
if [ -f "$php_exec" ]; then
|
||||
[ "$VERBOSE" = "1" ] && echo "-> php_exec $php_exec"
|
||||
|
||||
# Raw PHP without error_reporting flag.
|
||||
alias "${php_alias}r"="$php_exec"
|
||||
|
||||
# PHP with error_reporting flag.
|
||||
alias "$php_alias"="$php_exec $php_error_reporting"
|
||||
|
||||
# Local PHP Server.
|
||||
alias "${php_alias}s"="$php_exec -S localhost:9000"
|
||||
|
||||
# Use composer with specific PHP and error_reporting flag on.
|
||||
alias "${php_alias}c"="$php_exec $php_error_reporting $(which composer)"
|
||||
local php_exec="${HOMEBREW_PREFIX}/opt/${version}/bin/php"
|
||||
if [[ -x "$php_exec" ]]; then
|
||||
echo "alias ${php_alias}r='$php_exec'"
|
||||
echo "alias $php_alias='$php_exec $php_error_reporting'"
|
||||
echo "alias ${php_alias}s='$php_exec -S localhost:9000'"
|
||||
if [[ -n "$composer_path" ]]; then
|
||||
echo "alias ${php_alias}c='$php_exec $php_error_reporting $composer_path'"
|
||||
fi
|
||||
done
|
||||
else
|
||||
[[ "$VERBOSE" -ge 1 ]] && echo "Executable not found: $php_exec (skipping alias for $version)"
|
||||
fi
|
||||
done < "$brew_file"
|
||||
}
|
||||
|
||||
# Main function
|
||||
main()
|
||||
{
|
||||
local php_versions
|
||||
php_versions=($(get_php_versions))
|
||||
create_aliases "${php_versions[@]}"
|
||||
}
|
||||
# -----------------------------------------------------------------------------
|
||||
# Main Cache Update Logic
|
||||
#
|
||||
# If the brew list cache is stale (or missing), regenerate it and the aliases.
|
||||
# If only the alias cache is stale, regenerate just the alias cache.
|
||||
# -----------------------------------------------------------------------------
|
||||
if ! cache_is_valid "$BREW_LIST_CACHE" "$CACHE_TTL"; then
|
||||
[[ "$VERBOSE" -ge 1 ]] && echo "Brew list cache is stale or missing. Regenerating brew list and aliases."
|
||||
# Regenerate the brew list cache (filtering only PHP formulas).
|
||||
brew list | grep '^php' > "$BREW_LIST_CACHE"
|
||||
# Generate the aliases cache from the new brew list.
|
||||
generate_aliases "$BREW_LIST_CACHE" > "$ALIASES_CACHE"
|
||||
else
|
||||
[[ "$VERBOSE" -ge 1 ]] && echo "Using cached brew list from $BREW_LIST_CACHE."
|
||||
if ! cache_is_valid "$ALIASES_CACHE" "$CACHE_TTL"; then
|
||||
[[ "$VERBOSE" -ge 1 ]] && echo "Alias cache is stale or missing. Regenerating aliases."
|
||||
generate_aliases "$BREW_LIST_CACHE" > "$ALIASES_CACHE"
|
||||
fi
|
||||
fi
|
||||
|
||||
main "$@"
|
||||
# Source the cached alias definitions.
|
||||
if [[ -f "$ALIASES_CACHE" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$ALIASES_CACHE"
|
||||
[[ "$VERBOSE" -ge 1 ]] && echo "Aliases loaded from cache."
|
||||
else
|
||||
[[ "$VERBOSE" -ge 1 ]] && echo "No alias cache found; no aliases were loaded."
|
||||
fi
|
||||
|
||||
@@ -1,78 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# x-sha256sum-matcher
|
||||
#
|
||||
# Check if two files are the same
|
||||
# Compare two files by computing their SHA256 hashes.
|
||||
#
|
||||
# Ismo Vuorinen <https://github.com/ivuorinen> 2023
|
||||
# MIT License
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ENV Variables
|
||||
: "${VERBOSE:=0}" # VERBOSE=1 x-sha256sum-matcher file1 file2
|
||||
# Default settings
|
||||
VERBOSE=0
|
||||
|
||||
# Return sha256sum for file
|
||||
# $1 - filename (string)
|
||||
get_sha256sum()
|
||||
# Print usage/help message
|
||||
usage()
|
||||
{
|
||||
sha256sum "$1" | head -c 64
|
||||
cat << EOF
|
||||
Usage: $0 [options] file1 file2
|
||||
|
||||
Compare two files by computing their SHA256 hashes.
|
||||
|
||||
Options:
|
||||
-v Enable verbose output.
|
||||
-h, --help Display this help message and exit.
|
||||
EOF
|
||||
}
|
||||
|
||||
# Print message if VERBOSE is enabled
|
||||
# $1 - message (string)
|
||||
# Check if a command exists
|
||||
command_exists()
|
||||
{
|
||||
command -v "$1" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# Ensure sha256sum is available
|
||||
if ! command_exists sha256sum; then
|
||||
echo "Error: sha256sum command not found. Please install it." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Process command-line options
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-v)
|
||||
VERBOSE=1
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
echo "Error: Unknown option: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate input arguments: expect exactly 2 files
|
||||
if [[ $# -ne 2 ]]; then
|
||||
echo "Error: Two file arguments required." >&2
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
file1="$1"
|
||||
file2="$2"
|
||||
|
||||
# Check if files exist and are readable
|
||||
for file in "$file1" "$file2"; do
|
||||
if [[ ! -f "$file" ]]; then
|
||||
echo "Error: File does not exist: $file" >&2
|
||||
exit 1
|
||||
elif [[ ! -r "$file" ]]; then
|
||||
echo "Error: File is not readable: $file" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Print verbose messages if enabled
|
||||
msg()
|
||||
{
|
||||
[[ "$VERBOSE" -eq 1 ]] && echo "$1"
|
||||
}
|
||||
|
||||
# Print error message and exit
|
||||
# $1 - error message (string)
|
||||
error()
|
||||
{
|
||||
msg "(!) ERROR: $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Validate input arguments
|
||||
validate_inputs()
|
||||
{
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Usage: $0 file1 file2"
|
||||
exit 1
|
||||
if [[ "$VERBOSE" -eq 1 ]]; then
|
||||
echo "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if file exists
|
||||
# $1 - filename (string)
|
||||
check_file_exists()
|
||||
# Compute SHA256 hash for a file using awk to extract the first field
|
||||
get_sha256sum()
|
||||
{
|
||||
local filename=$1
|
||||
if [ ! -f "$filename" ]; then
|
||||
error "File does not exist: $filename"
|
||||
fi
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
}
|
||||
|
||||
# Main function
|
||||
main()
|
||||
{
|
||||
local file_1=$1
|
||||
local file_2=$2
|
||||
msg "Computing SHA256 for '$file1'..."
|
||||
hash1=$(get_sha256sum "$file1")
|
||||
msg "SHA256 for '$file1': $hash1"
|
||||
|
||||
validate_inputs "$file_1" "$file_2"
|
||||
check_file_exists "$file_1"
|
||||
check_file_exists "$file_2"
|
||||
msg "Computing SHA256 for '$file2'..."
|
||||
hash2=$(get_sha256sum "$file2")
|
||||
msg "SHA256 for '$file2': $hash2"
|
||||
|
||||
local file_1_hash
|
||||
local file_2_hash
|
||||
|
||||
file_1_hash=$(get_sha256sum "$file_1")
|
||||
file_2_hash=$(get_sha256sum "$file_2")
|
||||
|
||||
if [ "$file_1_hash" != "$file_2_hash" ]; then
|
||||
error "Files do not match"
|
||||
if [[ "$hash1" != "$hash2" ]]; then
|
||||
echo "Files do not match." >&2
|
||||
exit 1
|
||||
else
|
||||
msg "(*) Success: Files do match"
|
||||
msg "Success: Files match."
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -1,69 +1,192 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generate thumbnails using ImageMagick (magick)
|
||||
#
|
||||
# Generate thumbnails using ImageMagick (magick) with MIME type filtering.
|
||||
# https://imagemagick.org/script/download.php
|
||||
#
|
||||
# Defaults to current directory creating thumbnails with 1000x1000
|
||||
# dimensions and 200px white borders around the original image.
|
||||
# This script recursively processes images in a given directory (and its subdirectories)
|
||||
# by using the `mimetype` command to detect file types. Files with MIME types that are not
|
||||
# supported by ImageMagick (as defined in the ALLOWED_MIMETYPES array) are skipped.
|
||||
#
|
||||
# Defaults can be overridden with ENV variables like this:
|
||||
# $ THMB_BACKGROUND=black x-thumbgen ~/images/
|
||||
# Defaults (can be overridden by environment variables or command-line options):
|
||||
# THUMB_SOURCE: Directory with images (provided as a positional argument)
|
||||
# THUMB_OUTPUT: Directory to store thumbnails (default: same as THUMB_SOURCE)
|
||||
# THUMB_BACKGROUND: Background color (default: white)
|
||||
# THUMB_RESIZE: Resize dimensions (default: 800x800)
|
||||
# THUMB_EXTENT: Canvas dimensions (default: 1000x1000)
|
||||
# THUMB_SUFFIX: Suffix appended to filename (default: _thumb)
|
||||
#
|
||||
# Created by: Ismo Vuorinen <https://github.com/ivuorinen> 2015
|
||||
# Options:
|
||||
# -o output_directory Specify the output directory for thumbnails (default: same as source).
|
||||
# -s suffix Specify a custom suffix for thumbnail filenames (default: _thumb).
|
||||
# -h, --help Display this help message and exit.
|
||||
#
|
||||
# Example:
|
||||
# THUMB_BACKGROUND=black x-thumbgen.sh -o ~/thumbnails ~/images/
|
||||
#
|
||||
# Author: Ismo Vuorinen <https://github.com/ivuorinen> 2015
|
||||
# Improved in 2025
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Default values
|
||||
: "${THMB_SOURCE:=${1:-}}"
|
||||
: "${THMB_BACKGROUND:=white}"
|
||||
: "${THMB_RESIZE:=800x800}"
|
||||
: "${THMB_EXTENT:=1000x1000}"
|
||||
|
||||
# Print usage information
|
||||
usage()
|
||||
{
|
||||
echo "Usage: $0 /full/path/to/image/folder"
|
||||
cat << EOF
|
||||
Usage: $0 [options] source_directory
|
||||
|
||||
Options:
|
||||
-o output_directory Specify the output directory for thumbnails (default: same as source).
|
||||
-s suffix Specify a custom suffix for thumbnail filenames (default: _thumb).
|
||||
-h, --help Display this help message and exit.
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if ImageMagick is installed
|
||||
# Default values (can be overridden by ENV variables)
|
||||
THUMB_SOURCE=""
|
||||
THUMB_OUTPUT=""
|
||||
THUMB_BACKGROUND="${THUMB_BACKGROUND:-white}"
|
||||
THUMB_RESIZE="${THUMB_RESIZE:-800x800}"
|
||||
THUMB_EXTENT="${THUMB_EXTENT:-1000x1000}"
|
||||
THUMB_SUFFIX="${THUMB_SUFFIX:-_thumb}"
|
||||
|
||||
# List of MIME types supported by ImageMagick (adjust as needed)
|
||||
ALLOWED_MIMETYPES=("image/jpeg" "image/png" "image/gif" "image/bmp" "image/tiff" "image/webp")
|
||||
|
||||
check_magick_installed()
|
||||
{
|
||||
if ! command -v magick &> /dev/null; then
|
||||
echo "magick not found in PATH, https://imagemagick.org/script/download.php"
|
||||
echo "Error: 'magick' command not found. Please install ImageMagick from https://imagemagick.org/script/download.php" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate thumbnails
|
||||
generate_thumbnails()
|
||||
check_mimetype_installed()
|
||||
{
|
||||
local source=$1
|
||||
|
||||
magick \
|
||||
"${source}/*" \
|
||||
-resize "$THMB_RESIZE" \
|
||||
-background "$THMB_BACKGROUND" \
|
||||
-gravity center \
|
||||
-extent "$THMB_EXTENT" \
|
||||
-set filename:fname '%t_thumb.%e' +adjoin '%[filename:fname]'
|
||||
if ! command -v mimetype &> /dev/null; then
|
||||
echo "Error: 'mimetype' command not found. Please install it (e.g. via 'sudo apt install libfile-mimeinfo-perl' on Debian/Ubuntu)." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Main function
|
||||
main()
|
||||
# Helper function to check if a given MIME type is allowed
|
||||
is_supported_mimetype()
|
||||
{
|
||||
# Validate input
|
||||
if [ -z "$THMB_SOURCE" ]; then
|
||||
local mt=$1
|
||||
for allowed in "${ALLOWED_MIMETYPES[@]}"; do
|
||||
if [[ "$mt" == "$allowed" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Parse command-line options using getopts
|
||||
parse_options()
|
||||
{
|
||||
while getopts ":o:s:h-:" opt; do
|
||||
case $opt in
|
||||
o)
|
||||
THUMB_OUTPUT="$OPTARG"
|
||||
;;
|
||||
s)
|
||||
THUMB_SUFFIX="$OPTARG"
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
;;
|
||||
-)
|
||||
if [[ "$OPTARG" == "help" ]]; then
|
||||
usage
|
||||
else
|
||||
echo "Error: Unknown option --$OPTARG" >&2
|
||||
usage
|
||||
fi
|
||||
;;
|
||||
\?)
|
||||
echo "Error: Invalid option -$OPTARG" >&2
|
||||
usage
|
||||
;;
|
||||
:)
|
||||
echo "Error: Option -$OPTARG requires an argument." >&2
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
# The remaining argument should be the source directory.
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Error: Source directory is required." >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
# Check if the source directory is valid
|
||||
if [ ! -d "$THMB_SOURCE" ]; then
|
||||
echo "Invalid directory: $THMB_SOURCE"
|
||||
THUMB_SOURCE="$1"
|
||||
}
|
||||
|
||||
# Generate thumbnails recursively using find and filtering by MIME type
|
||||
generate_thumbnails()
|
||||
{
|
||||
local source_dir=$1
|
||||
local output_dir=$2
|
||||
|
||||
# Ensure the output directory exists (create if necessary)
|
||||
if [ ! -d "$output_dir" ]; then
|
||||
mkdir -p "$output_dir"
|
||||
fi
|
||||
|
||||
# Recursively find all files.
|
||||
while IFS= read -r -d '' file; do
|
||||
# Use mimetype to determine the file's MIME type.
|
||||
file_mimetype=$(mimetype -b "$file")
|
||||
if ! is_supported_mimetype "$file_mimetype"; then
|
||||
echo "Skipping unsupported MIME type '$file_mimetype' for file: $file" >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
# Determine the relative path with respect to the source directory.
|
||||
rel_path="${file#$source_dir/}"
|
||||
dir="$(dirname "$rel_path")"
|
||||
base="$(basename "$rel_path")"
|
||||
filename="${base%.*}"
|
||||
ext="${base##*.}"
|
||||
|
||||
# Create corresponding output subdirectory
|
||||
out_dir="${output_dir}/${dir}"
|
||||
mkdir -p "$out_dir"
|
||||
outfile="${out_dir}/${filename}${THUMB_SUFFIX}.${ext}"
|
||||
|
||||
echo "Processing '$file' -> '$outfile'..."
|
||||
magick "$file" \
|
||||
-resize "$THUMB_RESIZE" \
|
||||
-background "$THUMB_BACKGROUND" \
|
||||
-gravity center \
|
||||
-extent "$THUMB_EXTENT" \
|
||||
"$outfile"
|
||||
done < <(find "$source_dir" -type f -print0)
|
||||
}
|
||||
|
||||
main()
|
||||
{
|
||||
parse_options "$@"
|
||||
|
||||
if [ -z "$THUMB_SOURCE" ]; then
|
||||
echo "Error: Source directory not specified." >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
if [ ! -d "$THUMB_SOURCE" ]; then
|
||||
echo "Error: Source directory '$THUMB_SOURCE' does not exist or is not accessible." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If output directory is not specified, default to the source directory.
|
||||
if [ -z "$THUMB_OUTPUT" ]; then
|
||||
THUMB_OUTPUT="$THUMB_SOURCE"
|
||||
fi
|
||||
|
||||
check_magick_installed
|
||||
generate_thumbnails "$THMB_SOURCE"
|
||||
check_mimetype_installed
|
||||
generate_thumbnails "$THUMB_SOURCE" "$THUMB_OUTPUT"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -1,12 +1,92 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# About
|
||||
# -----
|
||||
# Repeat the command until it fails - always run at least once.
|
||||
# x-until-error: Repeatedly execute a command until it fails (non-zero exit status)
|
||||
#
|
||||
# Description:
|
||||
# This script executes the given command repeatedly until it returns a non-zero
|
||||
# exit status. It always runs the command at least once.
|
||||
#
|
||||
# This script is based on the original work by Steve Kemp.
|
||||
# Original work Copyright (c) 2013 by Steve Kemp.
|
||||
#
|
||||
# The code in the original repository may be modified and distributed under your choice of:
|
||||
# * The Perl Artistic License (http://dev.perl.org/licenses/artistic.html) or
|
||||
# * The GNU General Public License, version 2 or later (http://www.gnu.org/licenses/gpl2.txt).
|
||||
#
|
||||
# Modifications and enhancements by Ismo Vuorinen on 2025.
|
||||
#
|
||||
# Usage:
|
||||
# x-until-error [--sleep SECONDS] command [arguments...]
|
||||
#
|
||||
# Options:
|
||||
# --sleep SECONDS Wait SECONDS (default: 1) between command executions.
|
||||
# -h, --help Display this help message.
|
||||
#
|
||||
# Example:
|
||||
# x-until-error --sleep 2 ls -l
|
||||
|
||||
"$@"
|
||||
# Default sleep interval between executions.
|
||||
SLEEP_INTERVAL=1
|
||||
|
||||
# If the status code was zero then repeat.
|
||||
while [ $? -eq 0 ]; do
|
||||
"$@"
|
||||
# Function to display usage information.
|
||||
usage()
|
||||
{
|
||||
cat << EOF
|
||||
Usage: $0 [--sleep SECONDS] command [arguments...]
|
||||
|
||||
Repeats the given command until it fails (returns a non-zero exit status).
|
||||
|
||||
Options:
|
||||
--sleep SECONDS Wait SECONDS (default: 1) between command executions.
|
||||
-h, --help Display this help message.
|
||||
|
||||
Example:
|
||||
$0 --sleep 2 ls -l
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Parse command-line options.
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--sleep)
|
||||
shift
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Error: --sleep requires a numeric argument." >&2
|
||||
exit 1
|
||||
fi
|
||||
SLEEP_INTERVAL="$1"
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
;;
|
||||
--) # End of options marker.
|
||||
shift
|
||||
break
|
||||
;;
|
||||
-*)
|
||||
echo "Error: Unknown option: $1" >&2
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Ensure a command is provided.
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Error: No command specified." >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
# Execute the command repeatedly until it fails.
|
||||
while true; do
|
||||
"$@"
|
||||
status=$?
|
||||
if [ $status -ne 0 ]; then
|
||||
exit $status
|
||||
fi
|
||||
sleep "$SLEEP_INTERVAL"
|
||||
done
|
||||
|
||||
@@ -1,24 +1,92 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# About
|
||||
# -----
|
||||
# Repeat the command until it succeeds - always run at least once.
|
||||
# x-until-success: Repeat the command until it succeeds - always run at least once.
|
||||
#
|
||||
# This script is based on the original work by Steve Kemp.
|
||||
# Original work Copyright (c) 2013 by Steve Kemp.
|
||||
#
|
||||
# License
|
||||
# -------
|
||||
# The code in the original repository may be modified and distributed under your choice of:
|
||||
# * The Perl Artistic License (http://dev.perl.org/licenses/artistic.html) or
|
||||
# * The GNU General Public License, version 2 or later (http://www.gnu.org/licenses/gpl2.txt).
|
||||
#
|
||||
# Copyright (c) 2013 by Steve Kemp. All rights reserved.
|
||||
# Modifications and enhancements by Ismo Vuorinen on 2025.
|
||||
#
|
||||
# This script is free software; you can redistribute it and/or modify it under
|
||||
# the same terms as Perl itself.
|
||||
# Usage:
|
||||
# x-until-success [--sleep SECONDS] command [arguments...]
|
||||
#
|
||||
# The LICENSE file contains the full text of the license.
|
||||
# Options:
|
||||
# --sleep SECONDS Wait SECONDS (default: 1) between command executions.
|
||||
# -h, --help Display this help message.
|
||||
#
|
||||
# Example:
|
||||
# x-until-success --sleep 2 ls -l
|
||||
|
||||
# Run the first time.
|
||||
"$@"
|
||||
# Default sleep interval between command executions.
|
||||
SLEEP_INTERVAL=1
|
||||
|
||||
# If the status code was not zero then repeat.
|
||||
while [ $? -ne 0 ]; do
|
||||
"$@"
|
||||
# Display usage information.
|
||||
usage()
|
||||
{
|
||||
cat << EOF
|
||||
Usage: $0 [--sleep SECONDS] command [arguments...]
|
||||
|
||||
Repeats the given command until it succeeds (returns a zero exit status).
|
||||
The command is always executed at least once.
|
||||
|
||||
Options:
|
||||
--sleep SECONDS Wait SECONDS (default: 1) between command executions.
|
||||
-h, --help Display this help message.
|
||||
|
||||
Example:
|
||||
$0 --sleep 2 ping -c 1 google.com
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Parse command-line options.
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--sleep)
|
||||
shift
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Error: --sleep requires a numeric argument." >&2
|
||||
usage
|
||||
fi
|
||||
SLEEP_INTERVAL="$1"
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
-*)
|
||||
echo "Error: Unknown option: $1" >&2
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Ensure that a command is provided.
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Error: No command specified." >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
# Execute the command at least once.
|
||||
"$@"
|
||||
status=$?
|
||||
|
||||
# If the command did not succeed, repeat until it does.
|
||||
while [ $status -ne 0 ]; do
|
||||
sleep "$SLEEP_INTERVAL"
|
||||
"$@"
|
||||
status=$?
|
||||
done
|
||||
|
||||
exit $status
|
||||
|
||||
@@ -15,7 +15,7 @@ COLOR_P='\033[1;36m'
|
||||
COLOR_S='\033[0;36m'
|
||||
RESET='\033[0m'
|
||||
|
||||
# Print time-based personalized message, using figlet & lolcat if availible
|
||||
# Print time-based personalized message, using figlet & lolcat if available
|
||||
function welcome_greeting()
|
||||
{
|
||||
h=$(date +%H)
|
||||
@@ -51,7 +51,7 @@ function welcome_sysinfo()
|
||||
fi
|
||||
}
|
||||
|
||||
# Print todays info: Date, IP, weather, etc
|
||||
# Print today's info: Date, IP, weather, etc
|
||||
function welcome_today()
|
||||
{
|
||||
timeout=1
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Wait until a given host is down (determined by ping) then execute the
|
||||
# given command
|
||||
#
|
||||
# This script is based on the original work by Steve Kemp.
|
||||
# Original work Copyright (c) 2013 by Steve Kemp.
|
||||
#
|
||||
# The code in the original repository may be modified and distributed under your choice of:
|
||||
# * The Perl Artistic License (http://dev.perl.org/licenses/artistic.html) or
|
||||
# * The GNU General Public License, version 2 or later (http://www.gnu.org/licenses/gpl2.txt).
|
||||
#
|
||||
# Modifications and enhancements by Ismo Vuorinen on 2025.
|
||||
#
|
||||
# Usage:
|
||||
# ./when-down HOST COMMAND...
|
||||
#
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Wait until a given host is online (determined by ping) then execute the
|
||||
# given command
|
||||
#
|
||||
# This script is based on the original work by Steve Kemp.
|
||||
# Original work Copyright (c) 2013 by Steve Kemp.
|
||||
#
|
||||
# The code in the original repository may be modified and distributed under your choice of:
|
||||
# * The Perl Artistic License (http://dev.perl.org/licenses/artistic.html) or
|
||||
# * The GNU General Public License, version 2 or later (http://www.gnu.org/licenses/gpl2.txt).
|
||||
#
|
||||
# Modifications and enhancements by Ismo Vuorinen on 2025.
|
||||
#
|
||||
# Usage:
|
||||
# ./when-up HOST COMMAND...
|
||||
#
|
||||
|
||||
@@ -9,13 +9,13 @@ $dotfiles_env = getenv("DOTFILES") ?? '~/.dotfiles';
|
||||
$dest = "$dotfiles_env/docs/aerospace-keybindings.md";
|
||||
|
||||
exec("aerospace config --get mode --json", $output);
|
||||
$output = join(' ', $output);
|
||||
$output = implode(' ', $output);
|
||||
$config = json_decode($output, true);
|
||||
|
||||
$main = $config['main'];
|
||||
unset($config['main']);
|
||||
|
||||
function process_section(string $title, array $array)
|
||||
function process_section(string $title, array $array): string
|
||||
{
|
||||
$bindings = $array['binding'] ?? [];
|
||||
ksort($bindings);
|
||||
|
||||
Reference in New Issue
Block a user