backend/ubuntu

The Linux Command Line: 28. Reading keyboard input - 2. IFS, validation, menu

seul chan 2021. 5. 23. 22:43

IFS

보통 shell은 read의 input을 쪼개서 전달한다.

이는 IFS(Internal Field Seperator)라는 shell variable로 설정된다.

IFS는 기본값은 스페이스, 탭, newline characher이다.

이를 변경하여 read의 input을 나누는 필드를 변경할 수 있다.

예를들면 /etc/passwd 파일은 colon (:)을 필드 seperator로 사용한다. IFS를 single colon으로 변경하여 /etc/passwd의 내용을 read로 전달할 수 있다.

아래는 이를 구현한 스크립트.

#!/bin/bash

# read-ifs: read fields from a file

FILE=/etc/passwd

read -p "Enter a username > " user_name

file_info="$(grep "^$user_name:" $FILE)"

if [ -n "$file_info" ]; then
      IFS=":" read user pw uid gid name home shell <<< "$file_info"
      echo "User =      '$user'"
      echo "UID =       '$uid'"
      echo "GID =       '$gid'"
      echo "Full Name = '$name'"
      echo "Home Dir. = '$home'"
      echo "Shell =     '$shell'"
else
      echo "No such user '$user_name'" >&2
      exit 1
fi

file_info를 구하는 줄은 grep 명령 결과를 변수에 할당함.

IFS를 정의하는 줄에서는 변수할당, 읽기 명령, 리디렉션 연산자 세 부분으로 구성됨.

변수 할당

쉘은 하나 이상의 변수 할당이 명령 직전에 발생할 수 있게 해줌.

이는 일시적으로 명령이 실행되는 동안에만 환경이 변경되 게 함. 위 예시에서는 IFS 값을 콜론으로 변경.

한 줄이 아니라 아래처럼 해도 무방함

OLD_IFS = "$IFS"
IFS = ":"
read user pw uid gid name home shell <<< "$file_info"
IFS="$OLD_IFS"

<<< operator는 here string임. here string은 앞서 다룬 here document와 비슷하지만 single string만으로 구성되어있다.

위 예시에서는 /etc/passwd 파일이 read command의 표준 입력으로 제공.

굳이 here string을 사용한 이유는 read 명령어는 pipe로 사용할 수 없기 때문.

echo "foo" | read

위 명령어를 실행시켜보면 read가 pipe의 input을 받지 못해 실제로 아무것도 나오지 않는 것을 볼 수 있다.

이유는 쉘이 파이프라인을 처리하는 방식과 관련있는데, bash나 sh 등의 쉘에서 파이프라인은 subshell을 생성하기 때문.

unix-like 시스템에서 subshell은 실행중인 프로세스의 환경의 복사본을 만들어주고, 프로세스가 종료되면 이 환경의 복사본은 파괴됨. 이는 subshell이 부모 프로세스의 환경을 변경할 수 없음을 의미함.

Validating Input

아래는 input을 validate 해주는 예시. 지금까지 다룬 쉘 함수, [[ ]], (( )), control operator (&&), if 등을 모두 다룬 예시이기 때문에 한 번 따라 작성해보면 도움이 많이 된다.

#!/bin/bash

# read-validate: validate input

invalid_input () {
      echo "Invalid input '$REPLY'" >&2
      exit 1
}

read -p "Enter a single item > "

# input is empty (invalid)
[[ -z "$REPLY" ]] && invalid_input

# input is multiple items (invalid)
(( "$(echo "$REPLY" | wc -w)" > 1 )) && invalid_input

# is input a valid filename?
if [[ "$REPLY" =~ ^[-[:alnum:]\._]+$ ]]; then
      echo "'$REPLY' is a valid filename."
      if [[ -e "$REPLY" ]]; then
            echo "And file '$REPLY' exists."
      else
            echo "However, file '$REPLY' does not exist."
      fi

      # is input a floating point number?
      if [[ "$REPLY" =~ ^-?[[:digit:]]*\.[[:digit:]]+$ ]]; then
            echo "'$REPLY' is a floating point number."
      else
            echo "'$REPLY' is not a floating point number."
      fi

      # is input an integer?
      if [[ "$REPLY" =~ ^-?[[:digit:]]+$ ]]; then
            echo "'$REPLY' is an integer."
      else
            echo "'$REPLY' is not an integer."
      fi
else
      echo "The string '$REPLY' is not a valid filename."
fi

Menu

menu-driven은 interactivity의 흔한 방식으로, 사용자는 여러 선택지중에 하나를 고를 수 있다.

Please Select:

1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit

Enter selection [0-3] >

이전에 만든 sys_info_page 프로그램에 이 방식을 도입해서 menu-driven program으로 만들어보자.

#!/bin/bash

# read-menu: a menu driven system information program

clear
echo "
Please Select:

1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit
"

read -p "Enter selection [0-3] > "

if [[ "$REPLY" =~ ^[0-3]$ ]]; then
  if [[ "$REPLY" == 0 ]]; then
    echo "Program terminated."
    exit
  fi
  if [[ "$REPLY" == 1 ]]; then
    echo "Hostname: $HOSTNAME"
    uptime
    exit
  fi
  if [[ "$REPLY" == 2 ]]; then
    df -h
    exit
  fi
  if [[ "$REPLY" == 3 ]]; then
    if [[ "$(id -u)" -eq 0 ]]; then
      echo "Home Space Utilization (All Users)"
      du -sh /home/*
    else
      echo "Home Space Utilization ($USER)"
      du -sh "$HOME"
    fi
    exit
  fi
else
  echo "Invalid entry." >&2
  exit 1
fi

이를 실행시키면 user는 menu를 입력할 수 있게 되고, 입력받은 번호대로 스크립트가 실행됨.

$ ./read_menu
Please Select:

1. Display System Information
2. Display Disk Space
3. Display Home Space Utilization
0. Quit

Enter selection [0-3] > 1
Hostname: xxxx
22:42  up 4 days, 13:03, 3 users, load averages: 3.99 4.19 4.10