1. 파일 파싱

cut

cut {옵션} "파일이름"

-c 문자 위치를 기준으로 지정된 부분을 추출
-s <구분자> 구분자 사용
-d <구분자> 파일 내 필드로 따져서 추출
-f 필드 번호 기준으로 추출

ex) /etc/passwd 파일에서 첫 번째 필드와, 세 번째부터 다섯 번째 필드를 : 구분자를 기준으로 나누어서 마지막 5줄만 출력

ex) cut으로 첫 영역만 뽑기

paste

paste {옵션} "파일이름" "파일이름"

-s 한 파일 내용 한줄로 보여준 뒤 다른 파일 내용 보여줌
-d <구분자> 기본 구분자인 탭 대신 다른 문자로 열을 구분

diff

diff {옵션} "파일이름" "파일이름"

-b 스페이스 무시하고 비교

grep

grep {옵션} {패턴} "파일이름"

-v 패턴을 포함하지 않는 행 출력
-i 대소문자 구분 X
-n 줄 번호 함께 추력
-l 파일명 출력
-c 일치하는 라인 개수


sort

sort {옵션} "파일이름"

-f 대소문자 구분 X
-r 내림차순 정렬
-b 스페이스 무시
-k 필드 번호 표시
-t <구분자> 구부낮 사용
-n 숫자 순서로 정렬

sed

sed {옵션} "파일이름"

p 행 출력
d 선택한 행 삭제
-f 파일 안 내용 실행
s/가/나/g '가'문자열을 '나'문자열로 대체
-e 다중 편집
-q sed 종료





2. 쉘 스크립트

1) 쉘 스크립트 작성 전 체크리스트

  • 대상 Shell/OS 확인
    스크립트는 대상 Shell(bash,sh,zsh...)과 OS(RHEL,Ubuntu...)에 따라 동작과 옵션이 달라진다.
  • 언어 타입 고정
    작성 시 초반에 LANG 환경변수 초기값을 잡아줘야 출력 시 오류가 없다.
  • 기능별 함수화
    기능별 함수로 분리해서 테스트/수정/재사용에 용의하게 하자.
  • OS/배포판 별 분기
    OS/배포판에 따라 경로, 패키지명 등이 다르다. case 분기로 처리하자.

2) 쉘 종류


현재 쉘은 아래 명령어를 통해 확인할 수 있다.

echo $SHELL

3) 쉘 매개변수

$0은 현재 실행중인 Shell 프로그램이며 $1 ~ $숫자는 매개변수를 나타낸다.

4) 자동 쉘 변수

Shell에 의해 자동으로 지정되는 변수들이다.

5) 특수문자 인용

  • 백슬래쉬()
    일반적인 프로그래밍 언어에서 문자 출력시 사용할 때와 같다.
    백슬래쉬 뒤에 오는 문자를 그대로 출력한다.

  • 단일 인용부호(')
    단일 인용부호로 감싼 문자는 별도의 기능을 하지 않고 그대로 출력한다.
    백슬래쉬를 범위지정해서 사용한다 생각하면 편하다.

  • 이중 인용부호(")
    이중 인용부호로 묶은 문자는 \,`,",$ 를 제외하고 그대로 출력한다.

6) 조건문

&& -> 앞의 명령어가 참일 경우에만 뒤에 명령어 실행

|| -> 앞의 명령어가 거짓일 경우에만 뒤에 명령어 실행

; -> 앞의 명령어가 거짓이라도 뒤에 명령이 실행

7) 반복문

while -> 조건이 참일 경우에만 반복

while [ condition ]
do
  action
done

until -> 조건이 거짓일 경우에만 반복

Until [ end_condition ]
do
  action
done

for

for variable_name in value1 value2 ...
do
  action on $variable_name
done

8) 쉘 스크립트 연습

이론만 본다고 사용할 수 있는게 아니다 직접 작성해 보자...
1. 로그인/로그아웃 감지
해당 기능 구현을 위해 알아야 될 게 몇가지 있다.

tty -> 내부에서 쉘 접근
pts -> 원격 쉘 접근

tty/whoami를 통해 현재 사용자와 현재 실행중인 쉘이 몇 번 tty인지 알 수 있다.
who를 통해 현재 시스템의 전체 사용자/tty 정보를 알 수 있다.

#!/bin/bash

CURRENT_USER="$(whoami)" #현재 사용자
CURRENT_TTY="$(tty)" #현재 실행중인 쉘이 몇번 tty인가?

declare -A KNOWN_SESSIONS #연관 배열임 declare에 A옵션 키:값

# 스크립트 시작 시점에 로그인해 있던 세션을 기록해 둠
while read user tty _; do
    KNOWN_SESSIONS["$user:$tty"]=1
done < <(who) # who 실행 결과를 임시 파일 디스크립터로 만들고 그걸 while의 표준입력으로 써라

echo "$CURRENT_USER($CURRENT_TTY)에서 로그인/로그아웃 감시 시작"

while true; do
    #로그인 감지
    while read user tty _; do
        key="$user:$tty"

        if [ -z "${KNOWN_SESSIONS[$key]}" ]; then
            echo "사용자 '$user'가 $tty에서 로그인했습니다."
            KNOWN_SESSIONS["$key"]=1
        fi
    done < <(who)

    #로그아웃 감지
    unset CURRENT_SESSIONS
    declare -A CURRENT_SESSIONS

    while read user tty _; do
        CURRENT_SESSIONS["$user:$tty"]=1
    done < <(who)

    for key in "${!KNOWN_SESSIONS[@]}"; do
        if [ -z "${CURRENT_SESSIONS[$key]}" ]; then
            user="${key%:*}"
            tty="${key#*:}"
            echo "사용자 '$user'가 $tty에서 로그아웃했습니다."
            unset 'KNOWN_SESSIONS[$key]'
        fi
    done

    sleep 5  # 5초마다 확인
done
  • CURRENT_USER="$(whoami)"
    명령어를 변수에 넣을떄 $() 또는 백틱(`)으로 명령어를 감싸면 된다. 근데 $() 사용을 권장한다.

  • declare -A KNOWN_SESSIONS
    연관 배열 → 딕셔너리 생각하면 편함 키:값 쌍으로 저장하는거
    쉘 스크립트에서 연관 배열 사용하려면 declare -A 사용함

  • while ~ do ~ done < <(who)
    프로세스 치환 <(명령어) → 명령어 출력을 파일처럼 취급해 넘길 때 사용한다.
    who 명령어의 결과를 프로세스 치환을 통해 while의 표준입력(<)으로 넣는다.
    who 실행 결과를 임시 파일 디스크립터로 만들고 그걸 while의 표준입력으로 써라

  • while read user tty _; do
    while문 조건절에서 who의 값을 read로 받는다.
    who 명령어는 wiker pts/0 2025-11-02 23:37 (192.168.72.1) 해당 형태로 들어온다 .

    user에 첫번쨰 값인 사용자 이름(wiker)을 tty에 터미널 정보(pts/0)을 받는다. 이후 _를 통해 그 뒤 값은 무시한다. (_는 관용적으로 나머지는 안 쓴다는 뜻으로 쓴다고 한다)

  • KNOWN_SESSIONS["$user:$tty"]=1
    KNOWN_SESSIONS 연관 배열에 키(user:tty):값(1) 형태로 넣는다.

  • key="$user:$tty”
    프로세스 치환으로 who 결과 while에 표준입력으로 받아서 read를 통해 user, tty, _에 값을 받았고 key라는 변수에 user:tty 형태로 값을 저장함 → 연관배열 KNOWN_SESSIONS의 key 값임

  • if [ -z "${KNOWN_SESSIONS[$key]}" ]; then
    if문에서 -z는 -z "$VAR” 형태로 쓰이며 $VAR가 비었으면 참을 반환함
    여기서 만약 key값이 연관 배열에 없다? 그럼 처음 들어온 값임을 알수 있다.

    KNOWN_SESSIONS["$key"]=1

    그럼 echo를 통해 텍스트 출력후 key값에 value를 1을 넣어 저장하여 연관 배열에 기록함

  1. su로 사용자 변경 감지
#!/bin/bash

LOG=/var/log/secure

#tail로 로그 읽는데 -n 0 넣어서 이전거 무시 -F로 지속적으로 읽어오기 |로 결과 while로 넘김

sudo tail -n 0 -F "$LOG" | \
while read line; do
    time=$(echo "$line" | awk '{printf "%s %s %s", $1, $2, $3}') #시간

    case "$line" in
        *"pam_unix(su:session): session opened for user "*) #su로 계정 변경
            after_user=$(echo "$line" | awk -F'for user ' '{print $2}' | awk '{print $1}') #이동할 목적지
            before_user=$(echo "$line" | awk -F' by ' '{print $2}') #출발지
            echo "[$time] SU OPENED: $before_user -> $after_user"
            ;;
        *"pam_unix(su:session): session closed for user "*) #exit로 원래 계정으로 복귀
            before_user=$(echo "$line" | awk -F'for user ' '{print $2}' ) #출발지
            echo "[$time]SU CLOSED: $before_user"
            ;;
        *) #case default 동작 x
            :
            ;;
    esac
done
  • unset CURRENT_SESSIONS
    declare -A CURRENT_SESSIONS

    CURRENT_SESSIONS에 현재 who에 값을 저장하는데 unset을 통해 한번 비워준 이유는 루프를 돌떄 이전값을 초기화하기 위해서 이다.

  • for key in "${!KNOWN_SESSIONS[@]}"; do

    for 문에서 !KNOWN_SESSIONS[@] 는 연관배열 KNOWN_SESSIONS의 모든 값(@)들에서 !는 키값을 가져와라 라는 뜻

    KNOWN_SESSIONS의 모든 값에서 키값을 하나씩 가져와 key라는 변수에 넣는다

  • user="${key%:*}" tty="${key#*:}"

    key가 wiker:pts/0 일 때

    %는 뒤에 문자 때는 거 → %:* 는 :부터 뒤에 모든값 때는거 → wiker가 저장됨

    #은 앞에 문자 때는 거 → #*: 는 :부터 앞에 모든 문자 때는 거 → pts/0이 저장됨

    추가로 %%, ##는 가장 먼 값 기준으로 땜 ex) key가 wiker:user1:user2:pts/0 일때 %%:을 쓰면 wiker만 남고 %:를 쓰면 wiker:user1:user2가 남는다.

  • unset 'KNOWN_SESSIONS[$key]'

    unset은 배열 값 해제할때 씀 key라는 키값을 가진 거 해제

0개의 댓글