DIY dashboard on linux

터미널 실행시 첫 화면을 커스텀해보자

By widehyo
  • 리눅스 터미널을 켤 때마다 C드라이브의 사용량이 자동으로 보이는 대시보드를 만들 것이다
~ $ source ~/.bashrc

===== DISK DASHBOARD =====
C Drive Usage (2026-03-08)
[##################################------] 85%
Used: 404.6GB / 476.8GB

발단

  • 필자는 주로 WSL2에서 터미널을 다루고 있는데, 가끔 C:\, D:\ 드라이브에 남은 용량이 없어서 디스크를 정리해야 할 때가 있다.
  • 그럴 때면 주로 작업하는 디렉터리 및 window의 사용자 디렉터리나 %AppData%의 cache 디렉터리로 이동하여 du -sh *로 각 디렉터리 중 불필요하게 큰 용량을 차지하는 부분을 자세히 조사하여 필요 없는 파일을 삭제하곤 한다
  • 삭제 목표는 주로 df의 출력에서 C:\AvailableUse%를 높이는 것으로 설정한다
  • 그러던 어느날 df로 당시의 가용용량을 파악하는 것을 넘어 매일 df의 출력을 저장해 두면 그 추이를 나중에 확인할 때 편할 것이라는 생각이 들었다

전개

df 출력 전처리

  • 자 그럼 시작해보자.
    • awk를 사용할수 있으므로 먼저 df의 출력을 awk로 가공하자
    • 먼저 df의 manual page(man df)를 보면 df의 각 필드에 대한 설명이 나온다
         --output[=FIELD_LIST]
                use the output format defined by FIELD_LIST, or print all fields if FIELD_LIST is omitted.
         FIELD_LIST  is  a  comma-separated list of columns to be included.  Valid field names are: 'source', 'fstype', 'itotal', 'iused', 'iavail', 'ipcent', 'size', 'used', 'avail', 'pcent', 'file' and 'target' (see info page).
      
    • df의 기본 출력이 어떤 field를 보여주는지 --output을 통해 검증하자
~ $ df
Filesystem      1K-blocks      Used Available Use% Mounted on
drivers         499952636 424267596  75685040  85% /usr/lib/wsl/drivers
/dev/sdd       1055762868 134215988 867843408  14% /
C:\             499952636 424267596  75685040  85% /mnt/c
snapfuse              128       128         0 100% /snap/bare/5
snapfuse           340224    340224         0 100% /snap/code/219
snapfuse            65408     65408         0 100% /snap/core20/2686
snapfuse            75776     75776         0 100% /snap/core22/2216
tmpfs              800380         8    800372   1% /run/user/1000
...
~ $ df --output=source,size,used,avail,pcent,target
Filesystem      1K-blocks      Used     Avail Use% Mounted on
drivers         499952636 424269376  75683260  85% /usr/lib/wsl/drivers
/dev/sdd       1055762868 134216048 867843348  14% /
C:\             499952636 424269376  75683260  85% /mnt/c
snapfuse              128       128         0 100% /snap/bare/5
snapfuse           340224    340224         0 100% /snap/code/219
snapfuse            65408     65408         0 100% /snap/core20/2686
snapfuse            75776     75776         0 100% /snap/core22/2216
tmpfs              800380         8    800372   1% /run/user/1000
...
  • df의 기본 출력은 source,size,used,avail,pcent,target 필드라는 것을 알 수 있다.
  • df의 output은 형식을 가진 문자열(formatted string)이므로, csv 형태로 가공하는 awk 스크립트를 만들자
function join(arr, sep) {
  acc = arr[1]
  for (i = 2; i <= length(arr); i++) {
    acc = acc sep arr[i]
  }
  return acc
}

NR == 1 {
  cmd = "date -I"
  cmd | getline date
  close(cmd)
  print "date,source,size,used,avail,pcent,target"
  next
}

{
  split($0, arr)
  str = join(arr, ",")
  printf "%s,%s\n",date,str
}
  • YYYY-MM-DD 형식을 만드는 가장 편한 방법은 date -I를 이용하는 것이다
    • awk 안에서 bash command를 파이프와 getline 그리고 출력을 받는 변수로 연결하면 output을 변수에 저장할수 있다
    • 이 방법을 사용할 때는 반드시 command를 닫도록 주의하자
  • 각 필드를 “,”로 join하는 방법은 printf "%s,%s,...",$1,$2,... 도 있지만 $0을 한번 더 split하여 arr로 만든 후 join하는 방법도 있다. 여기서는 후자를 채택하였다
~ $ df | awk -f $HOME/.cli/awk/df_to_csv.awk
date,source,size,used,avail,pcent,target
2026-03-08,drivers,499952636,424269604,75683032,85%,/usr/lib/wsl/drivers
2026-03-08,/dev/sdd,1055762868,134216088,867843308,14%,/
2026-03-08,C:\,499952636,424269604,75683032,85%,/mnt/c
2026-03-08,snapfuse,128,128,0,100%,/snap/bare/5
2026-03-08,snapfuse,340224,340224,0,100%,/snap/code/219
2026-03-08,snapfuse,65408,65408,0,100%,/snap/core20/2686
2026-03-08,snapfuse,75776,75776,0,100%,/snap/core22/2216
2026-03-08,tmpfs,800380,8,800372,1%,/run/user/1000
...

DB 저장

  • 이제 이 출력을 어딘가에 저장해야 한다
    • 하루에 최대 한번만 snapshot을 저장하고 레코드가 누적되는 구조이기 때문에 database가 성격에 맞다
    • 그렇다고 외부에서 접속하는 기능은 필요 없으므로 postgresql씩은 필요 없을 것 같다
    • 어디까지나 local machine에 대한 상태를 DB로 관리하기 위함이니 local에서 파일로 관리되는 sqlite3duckdb가 성격에 맞을 것 같다
    • 사용하는 명령어의 성격이 bash native에 가까우니 duckdb까지 의존성을 추가할 필요는 없을것 같으니 sqlite3를 채택한다
  • db path는 ~/machine.db로 정했다. 목적을 잘 반영하는 것 같다.
    • 전략은 sqlite의 .mode csv .import /path/to/target.csv target_table를 이용해 csv 내용을 table에 넣는 것이다
  • 위에서 만든 df | awk -f $HOME/.cli/awk/df_to_csv.awk의 출력을 이용해 만든 csvsql로 변환하자
$ df | awk -f $HOME/.cli/awk/df_to_csv.awk > ~/df.csv
  • csv의 header만 읽어 table을 create하는 sql로 전환하는 awk는 만들어두면 사용할 일이 많을 것 같다
$ awk -f $HOME/.cli/awk/csv2createsql.awk ~/df.csv
$ cat $HOME/.cli/awk/csv2createsql.awk
function join(arr, sep) {
  acc = arr[1]
  for (i = 2; i <= length(arr); i++) {
    acc = acc sep arr[i]
  }
  return acc
}

function strip(str) {
  gsub(/^\s+|\s+$/, "", str)
  return str
}

### if not using gawk but awk
# NR == 1 {
#   print "create table " table_name "("
#   str = join(arr, " text,\n")
#   split($0, arr, ",")
#   str = join(arr, " text,\n")
#   print strip(str) " text"
#   print ");"
# }

### FINENAME, FNR are supported only if using gawk
FNR == 1 {
  filename = path_arr[split(FILENAME,path_arr,"/")]
  table_name = substr(filename, 1, index(filename, ".") - 1)
  split($0, arr, ",")
  print "create table " table_name "("
  str = join(arr, " text,\n")
  print strip(str) " text"
  print ");"
}

~ $ awk -f $HOME/.cli/awk/csv2createsql.awk df.csv
create table df(
date text,
source text,
size text,
used text,
avail text,
pcent text,
target text
);
  • 위의 awk script는 gawk에서만 제공되는 FILENAME 변수를 사용한 것에 주의하자.
    • 그냥 awk로는 awk -v table_name=df-f csv2createsql.awk df.csv 같은 형식으로 사용하면 된다
  • 이제 메인 shell file을 만들자 recorddf
#!/bin/bash
df_to_csv_file="$HOME/.cli/awk/df_to_csv.awk"
today=$(date -I)
tmpfile=$(mktemp)

sqlite3 ~/machine.db -cmd "
create table if not exists df(
date text,
source text,
size text,
used text,
avail text,
pcent text,
target text
);
" ".quit"
sqlite3 ~/machine.db -cmd "delete from df where date = '$today'" ".quit"
df | awk -f "$df_to_csv_file" > "$tmpfile"
sqlite3 ~/machine.db \
    -cmd ".mode csv" \
    -cmd ".import --skip 1 $tmpfile df" \
    ".quit"
sqlite3 ~/machine.db -cmd "select count(1) from df where date = '$today'" ".quit"
  • sqlite3-cmd 옵션은 sqlite3로 진입한 interactive 환경에서 실행하는 것과 같다
    • 실제로 위의 shell 파일을 만들 때 interactive 환경에서 여러번 시도하며 작성했다.
  • 이제 위 shell file을 실행가능한 위치에 넣고 PATH에 추가한다
    • export PATH=$PATH:$HOME/.cli/bin
    • cp recorddf $HOME/.cli/bin

대시보드 생성 및 자동 실행

  • 한동안 위의 설정으로 상태를 저장만 해 오다가 어느날 위의 스크립트를 자동으로 실행하고 주된 관심사인 C:\, D:\ 드라이브의 사용량을 bar plot으로 보면 좋겠다는 생각이 들었다
  • 먼저 나의 sqlite3 schema와 저장된 형태를 gpt에게 주고 bar plot을 생성하는 스크립트를 만들어 달라고 했더니 다음과 같은 스크립트가 나왔다
#!/bin/bash
DB="$HOME/machine.db"
TODAY=$(date -I)

ROW=$(sqlite3 "$DB" "
SELECT size, used, pcent
FROM df
WHERE date = '$TODAY'
  AND source = 'C:\\'
LIMIT 1;
")

if [ -n "$ROW" ]; then
    TOTAL=$(echo "$ROW" | cut -d'|' -f1)
    USED=$(echo "$ROW" | cut -d'|' -f2)
    PERCENT=$(echo "$ROW" | cut -d'|' -f3 | tr -d '%')

    BAR_WIDTH=40
    FILLED=$((PERCENT * BAR_WIDTH / 100))
    EMPTY=$((BAR_WIDTH - FILLED))

    BAR=$(printf "%0.s#" $(seq 1 $FILLED))
    SPACE=$(printf "%0.s-" $(seq 1 $EMPTY))

    echo ""
    echo "===== DISK DASHBOARD ====="
    echo "C Drive Usage ($TODAY)"
    printf "[%s%s] %s%%\n" "$BAR" "$SPACE" "$PERCENT"
    printf "Used: %.1fGB / %.1fGB\n" \
        "$(echo "$USED / 1024 / 1024" | bc -l)" \
        "$(echo "$TOTAL / 1024 / 1024" | bc -l)"
    echo ""
fi
  • 정상동작을 확인했으므로, 자동으로 실행하도록 설정만 하면 된다
    • ~/.bashrc에 추가하면 된다
    • $ echo "recorddf 1>/dev/null" >> ~/.bashrc
    • $ echo "bash ~/.cli/bin/diskdashboard" >> ~/.bashrc
  • 이제 리눅스 터미널을 켤 때마다 C드라이브의 사용량이 자동으로 보인다