24 điểm bởi GN⁺ 2025-10-23 | 2 bình luận | Chia sẻ qua WhatsApp
  • Giới thiệu những script shell được dùng nhiều nhất trong số nhiều script khác nhau đã viết khi duy trì dotfiles hơn 10 năm
  • Được chia thành các nhóm clipboard · quản lý tệp · Internet · xử lý văn bản · trình khởi chạy REPL · ngày/giờ · AV · tiến trình · tham chiếu nhanh · hệ thống · linh tinh, và mỗi script được trình bày theo dạng wrapper ngắn gọn cùng ví dụ thực chiến
  • Phần lớn script hoạt động trên macOS và Linux, với triết lý cốt lõi là “làm phẳng những phiền toái lặt vặt của các công cụ sẵn có”
    • Tích hợp các tiện ích tiêu chuẩn như pbcopy/xclip, python3 -m http.server, yt-dlp, ffmpeg, mpv
  • Những script được dùng nhiều nhất gồm copy/pasta/pastas/cpwd, mkcd/tempe/trash/mksh, serveit/getsong/getpod/getsubs, scratch/straightquote/markdownquote, timer/boop/tunes v.v.

Giới thiệu những script tôi dùng thường xuyên nhất khi duy trì dotfiles

  • Trong số nhiều script shell đã tạo ra suốt hơn 10 năm quản lý dotfiles cá nhân, bài viết này sắp xếp những script được dùng thường xuyên theo từng lĩnh vực
  • Mỗi script đều đi kèm mục đích, tần suất sử dụng và ví dụ tiêu biểu, giúp tăng khả năng áp dụng ngay lập tức
  • Mục tiêu chung là rút ngắn công việc lặp lại, trừu tượng hóa giữa các nền tảng, và cải thiện độ an toàn · khả năng đọc

Script liên quan đến clipboard

  • copypasta: wrapper bọc quanh trình quản lý clipboard của hệ thống, dựa trên pbcopy của macOS hoặc xclip của Linux
    • copy: sao chép đầu ra vào clipboard
    • pasta: lấy văn bản từ clipboard và in ra
    • Ví dụ: run_some_command | copy, pasta > file.txt, vim "$(pasta)", pasta | base64 --decode
  • pastas: công cụ in ra nội dung mới theo thời gian thực mỗi khi trạng thái clipboard thay đổi
    • Hữu ích khi lưu tất cả liên kết đã sao chép vào một tệp, hoặc tải hàng loạt nhiều liên kết
    • Ví dụ: pastas > everything_i_copied.txt, pastas | wget -i -
  • cpwd: sao chép đường dẫn thư mục hiện tại vào clipboard
    • Tiện khi di chuyển thư mục giữa nhiều tab terminal

Script quản lý tệp

  • mkcd foo: tạo thư mục và di chuyển vào ngay lập tức (rút gọn của mkdir foo && cd foo)
  • tempe: chuyển vào thư mục tạm (cd "$(mktemp -d)"), không cần dọn dẹp khi làm việc tạm trong môi trường sandbox
    • Ví dụ:
      # Download a file and extract it  
      tempe  
      wget 'https://example.com/big_file.tar.xz'  
      tar -xf big_file.tar.xz  
      # ...do something with the file...  
      
      # Write a quick throwaway script to try something out  
      tempe  
      vim foo.py  
      python3 foo.py  
      
  • trash: chuyển tệp vào thùng rác (hỗ trợ macOS/Linux), giúp tránh sai sót so với rm đơn thuần
  • mksh: tạo tệp shell script mới, đặt quyền thực thi và mở ngay bằng trình soạn thảo

Script liên quan đến Internet

  • serveit: chạy máy chủ tệp tĩnh từ thư mục cục bộ (mặc định cổng 8000, có phương án thay thế nếu chưa cài Python)
  • getsong dùng yt-dlp để tải âm thanh với chất lượng tốt nhất
  • getpod là wrapper nhận video dưới dạng audio dành cho podcast
  • getsubs trích xuất phụ đề tiếng Anh với logic ưu tiên phụ đề chính thức và fallback sang phụ đề tự động. Phù hợp cho pipeline tóm tắt và mục đích sao lưu
  • wifi off/on/toggle: điều khiển Wi‑Fi hệ thống, dùng khi xử lý sự cố mạng
  • url: phân tích chuỗi URL để tách và trích xuất giao thức, hostname, đường dẫn, truy vấn, hash v.v.

Script xử lý văn bản

  • line 10: in ra dòng cụ thể từ stdin (tương tự head, tail)
  • scratch: dùng để nhanh chóng mở một buffer văn bản tạm trong Vim như $EDITOR $(mktemp), phù hợp cho ghi chú một lần hoặc các tác vụ chuyển đổi nhỏ
  • straightquote: chuyển dấu ngoặc kép/thể hiện trích dẫn kiểu thông minh thành dấu ngoặc thẳng thông thường, tránh lỗi dấu ngoặc trong code và giảm kích thước tệp
  • markdownquote: thêm > vào đầu mỗi dòng để tạo blockquote Markdown
  • length: trả về độ dài của chuỗi đầu vào (có thể thay bằng wc -c)
  • jsonformat: in dữ liệu JSON theo dạng đẹp
  • uppered/lowered: chuyển chuỗi sang chữ hoa/chữ thường
  • nato bar: chuyển chuỗi đầu vào sang bảng mã chữ cái NATO (Bravo Alfa Romeo v.v.)
  • u+ 2025: tra tên và ký hiệu của ký tự Unicode
  • snippets foo: gọi ra cụm viết tắt cụ thể từ bộ snippet cá nhân
    • snippet arrow là mũi tên →, snippet recruiter là các mẫu tin nhắn như “not interested”

Liên quan đến trình khởi chạy REPL

  • Lấy cảm hứng từ irb của Ruby để chạy nhanh REPL của nhiều ngôn ngữ khác nhau:
    • iclj: Clojure
    • ijs: Deno (nếu không có thì dùng Node)
    • iphp: PHP
    • ipy: Python
    • isql: SQLite (chế độ in-memory trong Bash)

Script ngày và giờ

  • hoy: in ngày hiện tại theo định dạng ISO (ví dụ: 2020-04-20), dùng làm tiền tố cho tên tệp v.v.
  • timer 10m: bộ hẹn giờ (10 phút v.v.), khi hoàn tất sẽ phát âm thanh và gửi thông báo của HĐH
  • rn: dùng datecal để in thời gian hiện tại và lịch tháng theo cách dễ đọc

Xử lý âm thanh, video, hình ảnh

  • ocr: trích xuất văn bản từ tệp hình ảnh trên macOS (dự kiến mở rộng thêm)
  • boop: thông báo bằng âm thanh tùy theo lệnh trước đó thành công/thất bại (hữu ích trực quan sau khi chạy test v.v.)
  • sfx: phát một tệp hiệu ứng âm thanh cụ thể (.ogg), liên kết với boop, timer
  • tunes: phát tệp âm thanh bằng mpv (hỗ trợ shuffle)
  • pix: xem ảnh bằng mpv
  • radio: trình khởi chạy nhanh các đài radio Internet ưa thích
  • speak: loại bỏ Markdown khỏi văn bản đọc từ stdin rồi chuyển thành giọng nói (TTS)
  • shrinkvid: nén tệp video bằng ffmpeg
  • removeexif: xóa dữ liệu EXIF khỏi JPEG, dự kiến hỗ trợ thêm nhiều định dạng sau này
  • tuivid: xem video ngay trong terminal, ít dùng thực tế nhưng là một tính năng độc đáo

Quản lý tiến trình

  • each: thay thế cho xargs, find ... -exec, giúp thực thi các lệnh phức tạp dễ dàng hơn
  • running foo: tìm các tiến trình đang chạy theo từ khóa chỉ định (PID, lệnh v.v.) rồi in ra theo dạng dễ đọc
  • murder: wrapper cho kill, tuần tự thoát dần từ tín hiệu nhẹ đến mạnh để cưỡng bức kết thúc. Giúp tránh sai sót khi yêu cầu chương trình dừng
  • waitfor $PID: chờ cho đến khi PID chỉ định kết thúc, duy trì trạng thái thức trong lúc chờ
  • bb my_command: chạy lệnh ở chế độ background thực sự, phù hợp để chạy daemon v.v.
  • prettypath: in $PATH xuống từng dòng để dễ nhìn toàn cảnh hơn (hữu ích khi debug)
  • tryna my_command/trynafail my_command: lặp lại cho đến khi lệnh thành công (run until success), hoặc cho đến khi thất bại (run until fail), có thể ứng dụng cho mạng và nhiều tác vụ tự động hóa khác

Công cụ tham chiếu nhanh

  • emoji: tìm kiếm và in emoji theo từ khóa
  • httpstatus: in danh sách toàn bộ mã trạng thái HTTP, xem mô tả của từng mã cụ thể
  • alphabet: in toàn bộ bảng chữ cái tiếng Anh viết thường và viết hoa (tần suất sử dụng cao hơn tưởng tượng)

Quản lý hệ thống

  • theme 0/theme 1: chuyển theme toàn hệ thống (dark/light), liên kết với Vim, Tmux v.v.
  • sleepybear: đưa hệ thống vào chế độ ngủ (macOS, Linux)
  • ds-destroy: xóa đệ quy các tệp .DS_Store, hữu ích khi dọn dẹp thư mục của người dùng macOS

Khác

  • catbin foo: xem trực tiếp mã nguồn của tệp trong PATH
  • notify: gửi thông báo ở cấp hệ điều hành, tiện để báo ngay khi tác vụ dài hoàn tất
  • uuid: tạo UUID phiên bản 4

Kết luận

  • Những script được giới thiệu trong bài là các công cụ mà tác giả thực sự dùng thường xuyên
  • Các script lệnh tắt tự viết rất hiệu quả trong tối ưu hiệu suất công việc, tránh sai sót, nâng cao năng suất
  • Bạn cũng nên thử tự tạo và sử dụng các script tự động hóa của riêng mình

2 bình luận

 
GN⁺ 2025-10-23
Ý kiến trên Hacker News
  • Lệnh trash a.txt b.png sẽ chuyển các tệp a.txtb.png vào thùng rác, hỗ trợ trên Mac và Linux. Cách tôi từng làm trước đây xử lý tuần tự từng tệp, nên sẽ nghe tiếng xóa cho mỗi tệp và chỉ có thể khôi phục tệp cuối cùng bằng ⌘Z trong Finder. Có thể cải tiến thêm, nhưng thực ra dùng lệnh trash chính thức được tích hợp sẵn trên macOS sẽ tiện hơn. Vì không dùng Finder nên không có âm thanh hay khôi phục bằng ⌘Z, nhưng nhanh hơn và vẫn hỗ trợ tính năng “Put Back”. Ngoài ra, để pretty-print JSON thì dùng jq thay vì node sẽ giải quyết được bằng đoạn mã ngắn hơn nhiều, và macOS gần đây đã cài sẵn jq. Tương tự, để in UUID thì nếu cần v4 UUID, dùng uuidgen là lựa chọn hợp lý (xem man page)

    • Nhiều khi dùng chức năng tích hợp sẵn còn tốt hơn script tự viết. Ví dụ, trong vim thay vì dùng markdownquote, chỉ cần chọn cột đầu tiên bằng ctrl-v rồi nhấn "i> " và escape là xong. Ngắn hơn và hiệu quả hơn. Tôi cũng thắc mắc vì sao u+ 2025 lại trả về ñ, trong khi giá trị Unicode thực tế là U+00F1. Và catbin foo cũng tương đương với cat "$(which foo)". Nếu dùng zsh thì cat =foo còn ngắn và mạnh hơn. Trong zsh, sau dấu = có thể tự động hoàn thành nên an toàn để dùng cả với lệnh dài. Tôi hay dùng như file =firefox, vim =myscript.sh

    • Tôi đoán là tác giả không biết đến uuidgen. Điều tôi thích ở việc chia sẻ những kiến thức hay cấu hình kiểu này là nó luôn làm lộ ra những điểm mù của mình, nên việc chia sẻ rất quan trọng

    • Python cũng có thể pretty-print JSON theo mặc định

      $ echo '{ "hello": "world" }' | python3 -m json.tool
      {
        "hello": "world"
      }
      
    • Cảm ơn vì thông tin về trash. Trước giờ tôi vẫn dùng AppleScript kiểu "tell app "Finder" to move {%s} to trash" để chuyển nhiều tệp vào thùng rác

    • Ngoài rm và trash, tôi cũng đề xuất rip như một lựa chọn khác liên kết dự án rip

  • Vòng đời của một lập trình viên thật thú vị. Lúc đầu chỉ dùng môi trường shell thuần túy, đến năm thứ 1–2 thì viết hàng trăm dòng script và bash alias. Giờ đã 15 năm trong nghề, tôi lại cố dùng shell mặc định nhiều nhất có thể, không dùng alias, và xử lý phần phức tạp bằng Python hoặc Go

    • Xu hướng này có lẽ không hẳn là một trạng thái giác ngộ gì, mà chỉ là lười thôi (tôi cũng y như vậy nên mới nói thế). Nhờ những đồng nghiệp thích đào sâu môi trường tùy biến mà tôi thường xuyên học được công cụ mới, và gần đây cũng đã thêm atuin, fzf vào Linux của mình

    • Tôi ghi alias và function vào dotfile để dùng như cách ghi nhớ/lưu lại các lệnh hay dùng. Bộ công cụ hay dùng được cập nhật liên tục, và cũng dễ chuyển sang workstation mới

    • Hồi chỉ có một máy nix, tôi rất muốn tùy biến nhiều thứ. Giờ dùng nhiều máy cùng lúc nên tôi chỉ cài các gói cần thiết để đồng bộ môi trường

    • Những thứ viết bằng Python tôi vẫn gọi là script. Tôi không nghĩ thuật ngữ script chỉ giới hạn ở shell script

    • Gần đây làm việc với các kỹ sư trẻ, nhìn họ dùng đủ loại dotfiles lại thấy “ngày xưa mình cũng từng như thế, và cũng thấy phiền thật”. Giờ tôi chọn lọc công cụ để dùng, tùy biến linh hoạt theo nhu cầu, và cũng tôn trọng phong cách của người khác

  • Tôi rất thích tìm thấy những bài đăng mẹo thực chiến kiểu này trên HN. Tôi tò mò người khác thực sự làm việc như thế nào và mình có thể học rồi áp dụng điều gì. Ban đầu có thể nghĩ “chắc không cần cho mình đâu”, nhưng khi một tác vụ trở nên dễ hơn thì chính tác vụ đó lại tạo ra workflow mới. Vì thế tôi thường cứ thử trước rồi giữ lại cái hợp. Tôi cũng thích phong cách của bài gốc — việc ghi kèm tần suất sử dụng thực sự rất thực tế. Với các việc đơn giản, tôi thường mở devtools trong trình duyệt và giải quyết bằng JavaScript. (Ví dụ như chuyển chuỗi sang chữ thường)

    • Sẽ rất thú vị nếu làm một phân tích cost-benefit thực tế, tính cả thời gian theo cách của tác giả lẫn cách của tôi, cùng chi phí tạo script/ghi nhớ/tham chiếu/migration

    • Hình cheat sheet phím tắt Bash này rất hữu ích

  • Thay vì script line, dùng sed để in dòng cụ thể còn đơn giản hơn

    sed -n 2p file
    

    Có thể in dòng thứ hai. Muốn in nhiều dòng thì

    sed -n 2,4p file
    

    cũng làm được, nên tiện hơn script line

    • Tôi thường phải ghép nhiều lệnh sed lại với nhau. Mỗi lần như vậy lại phải sửa lệnh sed đầu tiên liên tục. Đôi khi còn cần grep trước sed nữa, nên nếu chia nhỏ bằng cat, tail, head thì có thể dùng từng chức năng như module, linh hoạt hơn. Điều đó cũng hợp với triết lý Unix là để mỗi thứ chỉ làm một việc
  • Tôi có vài script đơn giản hay dùng. Ví dụ:

    #!/usr/bin/env bash
    # ~/bin/,dehex
    
    echo "$1" | xxd -r -p
    
    #!/usr/bin/env bash
    # ~/bin/,ht
    
    highlight() {
      # màu sắc: 30=đen, 31=đỏ, 32=xanh lá, ...
      escape=$(printf '\033')
      sed "s,$2,${escape}[$1m&${escape}[0m,g"
    }
    
    if [[ $# == 1 ]]; then
      highlight 31 $1
    elif [[ $# == 2 ]]; then
      highlight 31 $1 | highlight 32 $2
    elif [[ $# == 3 ]]; then
      highlight 31 $1 | highlight 32 $2 | highlight 35 $3
    elif [[ $# == 4 ]]; then
      highlight 31 $1 | highlight 32 $2 | highlight 35 $3 | highlight 36 $4
    fi
    

    Tôi đặt script cá nhân với tiền tố , (dấu phẩy) để chuyển nhanh. Tôi thấy việc định kỳ thống kê các script riêng của mình trong history và dọn bỏ những cái không còn dùng nữa là rất đáng làm

  • Tôi vẫn chưa khái quát hóa được, nhưng script unmv đang giúp tôi làm việc khá tiện

    #!/bin/sh
    if test "$#" != 2
    then
      echo 'Error: unmv must have exactly 2 arguments'
      exit 1
    fi
    exec mv "$2" "$1"
    
  • Có nhiều mẹo hay, nhưng nhìn chung tôi học và dùng các utility chuẩn (sed, awk, grep, xargs, v.v.). Lý do là tôi thường phải làm việc qua lại giữa nhiều hệ thống, mà script và alias cá nhân của tôi hầu hết không được cài ở đó. Với utility chuẩn thì gần như làm được mọi việc

    • Quá đồng cảm. Rồi bạn sẽ chỉ dùng chuẩn để có thể làm việc ở bất cứ đâu. Nhưng công cụ nào thực sự tốt thì cuối cùng cũng sẽ được cài sẵn hoặc dễ cài bằng apt-get. Theo tôi, một công cụ ở dạng package được quản lý tốt vẫn đáng mong muốn hơn là một bộ script cá nhân
  • Tôi chia sẻ script giải nén yêu thích nhất của mình

    # ex - archive extractor
    # cách dùng: ex <tệp>
    function ex() {
      if [ -f $1 ] ; then
      case $1 in
        *.tar.bz2) tar xjf $1 ;;
        *.tar.gz) tar xzf $1 ;;
        *.tar.xz) tar xf $1 ;;
        *.bz2) bunzip2 $1 ;;
        *.rar) unrar x $1 ;;
        *.gz) gunzip $1 ;;
        *.tar) tar xf $1 ;;
        *.tbz2) tar xjf $1 ;;
        *.tgz) tar xzf $1 ;;
        *.zip) unzip $1 ;;
        *.Z) uncompress $1;;
        *.7z) 7z x $1 ;;
        *) echo "'$1' cannot be extracted via ex()" ;;
      esac
      else
        echo "'$1' is not a valid file"
      fi
    }
    
    • Tôi cũng muốn làm một counterpart ngược lại để nén

    • Tôi dùng dtrx, nó tự động giải nén vào trong thư mục nên rất tiện

    • Tôi thấy aunpack tiện hơn

    • Gọn gàng thật

    • Nếu thêm cả inotify và systemd user service thì có thể tiến hóa thêm một bậc nữa. Cũng đã có phiên bản tồn tại dưới dạng package rồi. Tự làm lấy thì hơi giống tái phát minh bánh xe

  • Tôi có hai function luôn dùng để encode hoặc cắt mp4. Nhờ các flag này mà mức độ tương thích được tối đa hóa cho nhiều môi trường như WhatsApp, Discord trên di động, v.v.

    ffmp4() {
      input_file="$1"
      output_file="${input_file%.*}_sd.mp4"
    
      ffmpeg -i "$input_file" -c:v libx264 -crf 33 -profile:v baseline -level 3.0 -pix_fmt yuv420p -movflags faststart "$output_file"
    
      echo "Compressed video saved as: $output_file"
    }
    
    ffmp4 foo.webm  # chuyển thành foo_sd.mp4
    
    fftime() {
      input_file="$1"
      output_file="${input_file%.*}_cut.mp4"
      ffmpeg -i "$input_file" -c copy -ss "$2" -to "$3" "$output_file"
    
      echo "Cut video saved as: $output_file"
    }
    
    fftime foo.mp4 01:30 01:45  # tạo foo_cut.mp4
    

    fftime cắt rất nhanh mà không cần re-encode bản gốc, nhưng tùy video có thể gặp chút vấn đề (ví dụ không phát được). Nếu muốn re-encode thì bỏ -c copy đi

  • Mỗi khi tạo và test alias hay function, tôi thích áp dụng ngay ~/.zshrc, nên dùng alias như bên dưới cho tiện

    alias vz="vim ~/.zshrc && . ~/.zshrc"
    

    Và trên Mac, tôi dùng function sau để grep trong tệp docx

    docgrep() {
      mdfind "\"$@\"" -onlyin /Users/xxxx/Notes 2> >(grep --invert-match ' [UserQueryParser] ' >&2) | grep -v -e '/Inactive/' | sort
    }
    

    Ngoài ra, để ẩn danh clipboard trên Mac trước khi dán vào ChatGPT, Slack nội bộ hay các kênh công khai khác cho mục đích debug, tôi dùng function dưới đây. Khi chạy function, clipboard đã được chuyển đổi mới cũng sẽ được in ra stdout, nên có thể kiểm tra xem còn sót gì không

    anonymizeclipboard() {
      my_user_id=xxxx
      account_ids="1234567890|1234567890" #regex
      corp_words="xxxx|xxxx|xxxx|xxxx|xxxx" #regex
      project_names="xxxx|xxxx|xxxx|xxxx|xxxx" # regex
      pii="xxxx|xxxx|xxxx|xxxx|xxxx|xxxx" # regex
      hostnames="xxxx|xxxx|xxxx|xxxx|xxxx|xxxx|xxxx|xxxx|xxxx" # regex
      pbpaste | sed -E -e 's/([0-9]{1,3})\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/\1.x.x.x/g' \
      -e "s/(${corp_words}|${project_names}|${my_user_id}|${pii}|${hostnames})/xxxx/g" -e "s/(${account_ids})/1234567890/g" | pbcopy
      pbpaste
    }
    alias anon=anonymizeclipboard
    
    • Cái này hay thật. Tôi cũng hay gặp tình huống này mà chưa tìm được cách phù hợp nên khá khổ sở
 
krepe90 2025-10-24

Điều này cũng khiến tôi nhớ tới bài viết từng được đăng trên GeekNews: Ask GN: Bạn có shell snippet tự viết nào thường dùng không?