Animated Christmas tree for your terminal!
- Fully responsive to terminal resizing
- Animated falling snow
- Enhanced decorations
- Dynamic scaling
- File:
bashmastree_2026.sh
- Original simpler version
- Lighter on resources
- File:
bashmastree_2025.sh
Animated Christmas tree for your terminal!
bashmastree_2026.shbashmastree_2025.sh| #!/usr/bin/env bash | |
| # ------------------------------------------------------------------ | |
| # Script Name: christmas.sh | |
| # Description: A Bash Script to display a | |
| # Christmas tree in Terminal. | |
| # Website: https://gist.github.com/ostechnix | |
| # Version: 1 | |
| # Year: 2025 | |
| # Usage: chmod +x christmas.sh | |
| # ./christmas.sh | |
| # ------------------------------------------------------------------ | |
| # Clear the screen | |
| clear | |
| # Get terminal size | |
| rows=$(tput lines) | |
| cols=$(tput cols) | |
| # Function to print spaces | |
| print_spaces() { | |
| for ((space=0; space<$1; space++)); do | |
| echo -n " " | |
| done | |
| } | |
| # Function to print leaves with occasional blinking lights | |
| print_leaves() { | |
| for ((leaf=0; leaf<$1; leaf++)); do | |
| # Randomly decide to print a leaf or a light | |
| if (( RANDOM % 20 < 2 )); then # Adjust the frequency of lights here | |
| # Randomly choose a color for the blinking light | |
| case $(( RANDOM % 4 )) in | |
| 0) echo -n -e "\e[5;35mo\e[0m" ;; # Blinking Pink | |
| 1) echo -n -e "\e[5;34mo\e[0m" ;; # Blinking Blue | |
| 2) echo -n -e "\e[5;33mo\e[0m" ;; # Blinking Yellow | |
| 3) echo -n -e "\e[5;37mo\e[0m" ;; # Blinking White | |
| esac | |
| else | |
| echo -n -e "\e[32m*\e[0m" # Green color for * | |
| fi | |
| done | |
| } | |
| # Function to print the trunk | |
| print_trunk() { | |
| local trunk_width=${#1} | |
| local center=$((size - trunk_width / 2)) | |
| print_spaces $center | |
| echo -e "\e[33m$1\e[0m" # Brown color for the trunk | |
| } | |
| # Function to print the tree | |
| print_tree() { | |
| # Size of the tree | |
| size=10 | |
| # Calculate the starting row and column | |
| start_row=$(( (rows - size - 10) / 2 )) | |
| start_col=$(( (cols - 2 * size) / 2 )) | |
| # Loop to print the tree | |
| for ((i=1; i<=$size; i++)); do | |
| tput cup $((start_row + i)) $start_col | |
| print_spaces $((size-i)) | |
| print_leaves $((2*i-1)) | |
| echo | |
| done | |
| # Print the trunk | |
| trunk="HMH" | |
| for ((i=0; i<3; i++)); do | |
| tput cup $((start_row + size + i + 1)) $start_col | |
| print_trunk $trunk | |
| done | |
| } | |
| # Function to print the messages | |
| print_messages() { | |
| local message1="MERRY CHRISTMAS" | |
| local message2="HAPPY NEW YEAR 2025" | |
| local start_row1=$((start_row + size + 5)) | |
| local start_row2=$((start_row1 + 1)) | |
| local col1=$(( (cols - ${#message1}) / 2 )) | |
| local col2=$(( (cols - ${#message2}) / 2 )) | |
| # Print first message | |
| for ((i=0; i<=${#message1}; i++)); do | |
| tput cup $start_row1 $col1 | |
| echo -e "\e[1;36m${message1:0:i}\e[0m" | |
| sleep 0.2 | |
| done | |
| # Print second message | |
| for ((i=0; i<=${#message2}; i++)); do | |
| tput cup $start_row2 $col2 | |
| echo -e "\e[1;36m${message2:0:i}\e[0m" | |
| sleep 0.2 | |
| done | |
| } | |
| # Print exit instructions | |
| print_exit_instructions() { | |
| tput cup $((rows - 2)) 0 | |
| echo -e "\e[1;31mPress Ctrl+C to exit\e[0m" | |
| } | |
| # Main loop to keep refreshing the tree and display messages | |
| while true; do | |
| print_tree | |
| print_exit_instructions | |
| print_messages | |
| sleep 0.2 # Pause after the complete messages are shown | |
| done |
| #!/usr/bin/env bash | |
| # ------------------------------------------------------------------ | |
| # Script Name: christmas.sh | |
| # Description: Enhanced Christmas tree with snow and animations | |
| # Fully responsive to terminal resizing! | |
| # Optimized for dark terminal backgrounds! | |
| # Website: https://gist.github.com/ostechnix | |
| # Version: 2 (Responsive) | |
| # Year: 2026 | |
| # Usage: chmod +x christmas.sh | |
| # ./christmas.sh | |
| # Note: Recommended minimum terminal size: 80x25 | |
| # Tree scales automatically with window size | |
| # Best viewed on dark terminal themes | |
| # ------------------------------------------------------------------ | |
| # Hide cursor | |
| tput civis | |
| # Terminal size variables | |
| rows=0 | |
| cols=0 | |
| start_row=0 | |
| start_col=0 | |
| size=12 | |
| resize_flag=0 | |
| # Snow array | |
| declare -a snow_x | |
| declare -a snow_y | |
| snow_count=30 | |
| # Function to update terminal size | |
| update_terminal_size() { | |
| rows=$(tput lines) | |
| cols=$(tput cols) | |
| # Adjust tree size based on terminal size - more granular scaling | |
| if (( rows < 25 )); then | |
| size=6 | |
| elif (( rows < 30 )); then | |
| size=8 | |
| elif (( rows < 35 )); then | |
| size=10 | |
| elif (( rows < 40 )); then | |
| size=12 | |
| elif (( rows < 50 )); then | |
| size=15 | |
| elif (( rows < 60 )); then | |
| size=18 | |
| else | |
| size=20 # Maximum size for very large terminals | |
| fi | |
| # Adjust snow count based on terminal size | |
| local new_snow_count=$(( (rows * cols) / 80 )) | |
| if (( new_snow_count < 15 )); then | |
| new_snow_count=15 | |
| elif (( new_snow_count > 80 )); then | |
| new_snow_count=80 | |
| fi | |
| snow_count=$new_snow_count | |
| # Recalculate positions with new size | |
| start_row=$(( (rows - size - 12) / 2 )) | |
| if (( start_row < 0 )); then | |
| start_row=0 | |
| fi | |
| start_col=$(( (cols - 2 * size) / 2 )) | |
| if (( start_col < 0 )); then | |
| start_col=0 | |
| fi | |
| } | |
| # Initialize terminal size | |
| update_terminal_size | |
| # Initialize snow with dynamic count | |
| init_snow() { | |
| snow_x=() | |
| snow_y=() | |
| for ((i=0; i<snow_count; i++)); do | |
| snow_x[$i]=$((RANDOM % cols)) | |
| snow_y[$i]=$((RANDOM % rows)) | |
| done | |
| } | |
| init_snow | |
| # Handle window resize | |
| handle_resize() { | |
| resize_flag=1 | |
| } | |
| # Function to print spaces | |
| print_spaces() { | |
| for ((space=0; space<$1; space++)); do | |
| echo -n " " | |
| done | |
| } | |
| # Function to print leaves with ornaments and lights | |
| print_leaves() { | |
| for ((leaf=0; leaf<$1; leaf++)); do | |
| rand=$((RANDOM % 100)) | |
| if (( rand < 5 )); then | |
| # Blinking lights - vibrant colors for dark terminals | |
| case $(( RANDOM % 6 )) in | |
| 0) echo -n -e "\e[1;91mβ\e[0m" ;; # Bright Red | |
| 1) echo -n -e "\e[1;93mβ\e[0m" ;; # Bright Yellow | |
| 2) echo -n -e "\e[1;94mβ\e[0m" ;; # Bright Blue | |
| 3) echo -n -e "\e[1;95mβ\e[0m" ;; # Bright Magenta | |
| 4) echo -n -e "\e[1;96mβ\e[0m" ;; # Bright Cyan | |
| 5) echo -n -e "\e[1;97mβ\e[0m" ;; # Bright White | |
| esac | |
| elif (( rand < 8 )); then | |
| # Ornaments | |
| case $(( RANDOM % 4 )) in | |
| 0) echo -n -e "\e[1;31mβ\e[0m" ;; # Red ornament | |
| 1) echo -n -e "\e[1;33mβ\e[0m" ;; # Gold ornament | |
| 2) echo -n -e "\e[1;35mβ\e[0m" ;; # Purple ornament | |
| 3) echo -n -e "\e[1;36mβ\e[0m" ;; # Cyan ornament | |
| esac | |
| else | |
| # Green leaves | |
| case $(( RANDOM % 3 )) in | |
| 0) echo -n -e "\e[32m*\e[0m" ;; # Medium green | |
| 1) echo -n -e "\e[1;32m*\e[0m" ;; # Bright green | |
| 2) echo -n -e "\e[2;32m*\e[0m" ;; # Dim green | |
| esac | |
| fi | |
| done | |
| } | |
| # Function to print the trunk | |
| print_trunk() { | |
| local trunk_width=${#1} | |
| # Center trunk under tree: tree base center is at (size-1), trunk center should align | |
| local center=$((size - 1 - trunk_width / 2)) | |
| print_spaces $center | |
| echo -e "\e[38;5;94m$1\e[0m" # Brown color for the trunk | |
| } | |
| # Function to update and draw snow | |
| draw_snow() { | |
| # Calculate message area to avoid | |
| local message_start_row=$((start_row + size + 7)) | |
| local message_end_row=$((message_start_row + 3)) | |
| local exit_row=$((rows - 2)) | |
| for ((i=0; i<snow_count; i++)); do | |
| # Clear old position (only if within bounds) | |
| if (( snow_y[$i] >= 0 && snow_y[$i] < rows && snow_x[$i] >= 0 && snow_x[$i] < cols )); then | |
| # Don't clear if in message area or exit instruction area | |
| if (( snow_y[$i] < message_start_row || snow_y[$i] > message_end_row )) && (( snow_y[$i] != exit_row )); then | |
| tput cup ${snow_y[$i]} ${snow_x[$i]} 2>/dev/null | |
| echo -n " " | |
| fi | |
| fi | |
| # Update position | |
| snow_y[$i]=$((snow_y[$i] + 1)) | |
| # Reset if out of bounds | |
| if (( snow_y[$i] >= rows - 1 || snow_x[$i] >= cols )); then | |
| snow_y[$i]=0 | |
| snow_x[$i]=$((RANDOM % cols)) | |
| fi | |
| # Draw new position (avoid tree area, message area, AND exit instruction) | |
| if (( snow_y[$i] >= 0 && snow_y[$i] < rows && snow_x[$i] >= 0 && snow_x[$i] < cols )); then | |
| if (( (snow_y[$i] < start_row || snow_y[$i] > start_row + size + 5) && | |
| (snow_y[$i] < message_start_row || snow_y[$i] > message_end_row) && | |
| snow_y[$i] != exit_row )); then | |
| tput cup ${snow_y[$i]} ${snow_x[$i]} 2>/dev/null | |
| # White snow - natural and beautiful on dark terminals | |
| echo -n -e "\e[1;97mβ\e[0m" | |
| fi | |
| fi | |
| done | |
| } | |
| # Function to print the tree | |
| print_tree() { | |
| # Loop to print the tree (removed star on top) | |
| for ((i=1; i<=$size; i++)); do | |
| local row=$((start_row + i)) | |
| if (( row >= 0 && row < rows )); then | |
| tput cup $row $start_col 2>/dev/null | |
| print_spaces $((size-i)) | |
| print_leaves $((2*i-1)) | |
| fi | |
| done | |
| # Print the trunk - scale trunk height with tree size | |
| local trunk_height=4 | |
| if (( size > 12 )); then | |
| trunk_height=5 | |
| fi | |
| if (( size > 16 )); then | |
| trunk_height=6 | |
| fi | |
| trunk="|||" | |
| for ((i=0; i<trunk_height; i++)); do | |
| local row=$((start_row + size + i + 1)) | |
| if (( row >= 0 && row < rows )); then | |
| tput cup $row $start_col 2>/dev/null | |
| print_trunk $trunk | |
| fi | |
| done | |
| } | |
| # Function to print the messages with gradient effect | |
| print_messages() { | |
| local message1="π MERRY CHRISTMAS π" | |
| local message2="β¨ HAPPY NEW YEAR 2026 β¨" | |
| local start_row1=$((start_row + size + 7)) | |
| local start_row2=$((start_row1 + 2)) | |
| # Calculate proper centering (accounting for emoji display width) | |
| # Each emoji typically renders as 2 characters wide | |
| local msg1_text=" MERRY CHRISTMAS " | |
| local msg2_text=" HAPPY NEW YEAR 2026 " | |
| # Only print if within terminal bounds | |
| if (( start_row1 < rows - 3 && cols > 30 )); then | |
| local col1=$(( (cols - ${#msg1_text} - 4) / 2 )) # -4 for emoji width | |
| tput cup $start_row1 $col1 2>/dev/null | |
| echo -e "\e[1;91m$message1\e[0m" | |
| fi | |
| if (( start_row2 < rows - 3 && cols > 30 )); then | |
| local col2=$(( (cols - ${#msg2_text} - 4) / 2 )) | |
| tput cup $start_row2 $col2 2>/dev/null | |
| echo -e "\e[1;96m$message2\e[0m" | |
| fi | |
| } | |
| # Print exit instructions | |
| print_exit_instructions() { | |
| if (( rows > 2 )); then | |
| tput cup $((rows - 2)) 0 2>/dev/null | |
| # Red works well on both backgrounds | |
| echo -e "\e[1;31mβ Press Ctrl+C to exit β\e[0m" | |
| fi | |
| } | |
| # Cleanup function | |
| cleanup() { | |
| tput cnorm # Show cursor | |
| clear | |
| exit 0 | |
| } | |
| # Trap Ctrl+C and window resize | |
| trap cleanup INT TERM | |
| trap handle_resize WINCH | |
| # Initial clear and draw | |
| clear | |
| print_tree | |
| print_messages | |
| print_exit_instructions | |
| # Main animation loop | |
| frame=0 | |
| while true; do | |
| # Handle terminal resize | |
| if (( resize_flag == 1 )); then | |
| clear | |
| update_terminal_size | |
| init_snow | |
| print_tree | |
| print_messages | |
| print_exit_instructions | |
| resize_flag=0 | |
| fi | |
| # Redraw tree periodically for blinking effect | |
| if (( frame % 3 == 0 )); then | |
| print_tree | |
| print_messages | |
| print_exit_instructions | |
| fi | |
| # Update snow | |
| draw_snow | |
| frame=$((frame + 1)) | |
| sleep 0.15 | |
| done |
Display Animated Christmas Tree In Terminal: