Blog
-
Aggregate groups of values
Input text Groups by blank lines; headings (no value) split sectionsOutput 0 sections -
Remove duplicates
Original 0 linesResult 0 uniqueCleanup rulesApplied top to bottom -
Extract URLs from text
Input text Paste any text containing URLsExtracted URLs (editable) 0 foundOpen in groups of URLsButtons are generated after extraction or after editing the output. Each button opens one group (e.g., 1 = URLs 1–5, 2 = URLs 6–10). The last group opens the remaining URLs. -
Compare two lists
Result view shows cleaned lines. Click to edit original text.Result view shows cleaned lines. Click to edit original text. -
Bash script to check redirects
urlhops() { # Minimal deps: bash, curl, awk, sed, tr, head # Input: paste URLs separated by space/tab/comma/newline, finish with EMPTY LINE. # # TLS behavior: # - Default: INSECURE (curl -k). # - Secure mode: URLHOPS_SECURE=1 urlhops (no -k). local max_hops=30 local timeout=20 local title_bytes=250000 # Default insecure; secure mode disables -k local CURL_TLS="-k" [ "${URLHOPS_SECURE:-0}" = "1" ] && CURL_TLS="" # Colors (disable if not TTY or NO_COLOR set) local use_color=1 [[ ! -t 1 ]] && use_color=0 [[ -n "${NO_COLOR:-}" ]] && use_color=0 local RST='' BLD='' FG_LGT='' BG_GRN='' BG_RED='' BG_BLU='' BG_MAG='' BG_GRY='' if [ "$use_color" -eq 1 ]; then RST=$'\033[0m'; BLD=$'\033[1m' FG_LGT=$'\033[97m' BG_GRN=$'\033[42m'; BG_RED=$'\033[41m'; BG_BLU=$'\033[44m'; BG_MAG=$'\033[45m'; BG_GRY=$'\033[100m' fi # ASCII box drawing (Cygwin-safe) local TL='+' TR='+' BL='+' BR='+' H='-' V='|' _strip_ansi() { LC_ALL=C sed -r 's/\x1B\[[0-9;]*[mK]//g'; } _vislen() { printf '%s' "$1" | _strip_ansi | LC_ALL=C awk '{print length($0)}'; } _badge() { local code="$1" bg case "$code" in 2??) bg="$BG_GRN" ;; 3??) bg="$BG_BLU" ;; 4??) bg="$BG_RED" ;; 5??) bg="$BG_MAG" ;; *) bg="$BG_GRY" ;; esac printf '%s%s%s%s[ %3s ]%s' "$BLD" "$bg" "$FG_LGT" "$BLD" "$code" "$RST" } _normalize_url() { local u="$1" if [[ "$u" =~ ^https?:// ]]; then printf '%s' "$u" else printf 'http://%s' "$u" fi } _abs_url() { # Best-effort Location resolution without python local base="$1" loc="$2" [ -z "$loc" ] && { printf '%s' ""; return 0; } [[ "$loc" =~ ^https?:// ]] && { printf '%s' "$loc"; return 0; } local origin path dir origin="$(printf '%s' "$base" | LC_ALL=C sed -nE 's#^(https?://[^/]+).*#\1#p')" [ -z "$origin" ] && { printf '%s' "$loc"; return 0; } if [[ "$loc" == /* ]]; then printf '%s%s' "$origin" "$loc" return 0 fi path="$(printf '%s' "$base" | LC_ALL=C sed -nE 's#^https?://[^/]+(/.*)?#\1#p')" [ -z "$path" ] && path="/" dir="$(printf '%s' "$path" | LC_ALL=C sed -E 's#[^/]*$##')" [ -z "$dir" ] && dir="/" printf '%s%s%s' "$origin" "$dir" "$loc" } _status_and_location() { # Outputs: "<code>\n<location>\n" local url="$1" headers code location headers="$( curl $CURL_TLS -sS -D - -o /dev/null \ --max-time "$timeout" \ --connect-timeout "$timeout" \ -H 'User-Agent: urlhops/visual-1.2' \ "$url" 2>/dev/null )" if [ -z "$headers" ]; then printf '000\n\n' return 0 fi code="$(printf '%s' "$headers" | LC_ALL=C awk 'NR==1 {print $2}')" [ -z "$code" ] && code="000" location="$(printf '%s' "$headers" | LC_ALL=C awk 'BEGIN{IGNORECASE=1} /^Location:[[:space:]]*/ {sub(/^Location:[[:space:]]*/,""); gsub("\r",""); print; exit}')" printf '%s\n%s\n' "$code" "$location" } _fetch_title() { # Safe title extraction; returns empty if not found. local url="$1" html title html="$( curl $CURL_TLS -sS -L --compressed \ --max-time "$timeout" \ --connect-timeout "$timeout" \ -H 'User-Agent: urlhops/title-1.2' \ "$url" 2>/dev/null | head -c "$title_bytes" )" [ -z "$html" ] && { printf '%s' ""; return 0; } # Extract first <title>...</title>, case-insensitive, collapse whitespace. title="$(printf '%s' "$html" \ | tr '\n' ' ' \ | LC_ALL=C sed -nE 's/.*<[Tt][Ii][Tt][Ll][Ee][^>]*>([^<]{0,500})<[/][Tt][Ii][Tt][Ll][Ee][^>]*>.*/\1/p' \ | head -n 1 \ | LC_ALL=C sed -E 's/[[:space:]]+/ /g; s/^[[:space:]]+//; s/[[:space:]]+$//')" printf '%s' "$title" } _box_top() { printf '%s%*s%s\n' "$TL" "$1" '' "$TR" | tr ' ' "$H"; } _box_bot() { printf '%s%*s%s\n' "$BL" "$1" '' "$BR" | tr ' ' "$H"; } _box_row() { local w="$1" text="$2" local pad=$((w - $(_vislen "$text"))); (( pad < 0 )) && pad=0 printf '%s %s%*s %s\n' "$V" "$text" "$pad" "" "$V" } # BUCLE PRINCIPAL: repetir prompt -> procesar -> volver a preguntar while true; do # Visual separation before prompt echo echo echo "Paste URLs (space/tab/comma/newline). Finish with an EMPTY LINE:" # Read until blank line local urls=() line while IFS= read -r line; do [ -z "$line" ] && break line="${line//$'\t'/ }" line="${line//,/ }" # shellcheck disable=SC2206 local parts=($line) local p for p in "${parts[@]}"; do [ -n "$p" ] && urls+=("$p") done done if [ ${#urls[@]} -eq 0 ]; then echo "No URLs detected." continue fi local u for u in "${urls[@]}"; do local start="$(_normalize_url "$u")" # First hop (initial URL, always bold) local out code loc out="$(_status_and_location "$start")" code="$(printf '%s' "$out" | LC_ALL=C sed -n '1p')" loc="$(printf '%s' "$out" | LC_ALL=C sed -n '2p')" local lines=() lines+=("$(_badge "$code") ${BLD}${start}${RST}") local cur="$start" local hops=0 while [ $hops -lt $max_hops ]; do hops=$((hops+1)) if [[ "$code" =~ ^30[12378]$ ]] && [ -n "$loc" ]; then lines+=(" -> $loc") local next next="$(_abs_url "$cur" "$loc")" next="$(_normalize_url "$next")" [ "$next" = "$cur" ] && break cur="$next" out="$(_status_and_location "$cur")" code="$(printf '%s' "$out" | LC_ALL=C sed -n '1p')" loc="$(printf '%s' "$out" | LC_ALL=C sed -n '2p')" lines+=("$(_badge "$code") $cur") continue fi break done local title="$(_fetch_title "$cur")" [ -z "$title" ] && title="<empty>" lines+=("Title: $title") local w=0 s len for s in "${lines[@]}"; do len="$(_vislen "$s")" (( len > w )) && w=$len done w=$((w + 2)) _box_top "$w" for s in "${lines[@]}"; do _box_row "$w" "$s"; done _box_bot "$w" echo done done } urlhops -
Portadas del día
Una selección de las portadas de los periódicos de hoy. Son de Kiosko.net y las pongo aquí para verlas juntas. Si alguna está en blanco es porque no se ha publicado aun.