tail 명령어
파일의 마지막 부분 중 일부를 보여주는 명령어
파일의 내용을 보여주는 여러 명령어가 있다. cat은 파일의 전체 내용, head는 파일의 앞부분 중 일부, tail은 head와 비슷하게 파일의 마지막 일부를 보여준다.
tail 명령어를 옵션과 같이 잘 사용하면 실시간으로 파일에 업데이트되는 내용을 볼 수 있다.
# 출력할 마지막 줄 수 입력
# -n, --lines
tail -n 3 text.txt
tail --lines 3 text.txt
# 파일의 마지막 3줄을 읽는다.
tail -n +16 text.txt
# 파일의 16번째줄부터 마지막 줄까지 읽는다.
# -v, --verbose
tail -v text.txt
# 파일의 이름을 출력한다.
# -q, --quiet, --silent
tail -q text.txt
tail --quiet text.txt
tail --silent text.txt
# 파일의 이름을 출력하지 않는다.
# 기본적으로 파일 이름은 출력하지 않는다.
# -f, --flow
tail -f text.txt
# text.txt 파일의 마지막 줄을 실시간으로 읽어들인다.
# 실시간으로 로그 파일을 출력하는 용도로 많이 쓰이며 로그 파일이 업데이트되면 출력한다.
# -F
tail -F text.txt
tail -f --retry text.txt
# -F는 -f와 --retry 옵션의 조합으로 text.txt 파일이 없어도 반복해서 파일을 읽어들인다.
# -f를 사용해도 text.txt 파일이 없으면 에러 메시지를 출력하고 동작이 종료되지만 -F를 사용할 경우 대기 상태를 유지하며 종료되지 않는다.
# -s, --sleep-interval
tail -s 3 text.txt
tail --sleep-interval 3 text.txt
# -f는 특정 파일을 감시해서 업데이트될 때마다 출력하였는데, 업데이트 주기는 1초이다.
# -s와 --sleep-interval을 사용하게 되면 -f와 똑같은 역할을 하면서 업데이트 주기를 정할 수 있다.
# 위의 명령문을 통해 3초에 한번씩 파일의 업데이트 여부를 감시하게 된다.
nohup 명령어
리눅스에서 프로세스를 실행한 터미널과의 세션 연결이 종료되어도 지속적으로 동작할 수 있는 명령어이다.
기본적으로 터미널에서 로그아웃이 발생하면 리눅스는 터미널이 실행한 프로세스에 Hub Signal이라는 종료신호를 전달한다.
이 종료 신호를 전달받은 프로세스들은 종료되는 게 정상이지만,
nohup 명령어는 이러한 hub signal을 무시(ignore)하도록 만든다. (이름도 그래서 nohup이다)
종료 신호를 무시하기 때문에 터미널과의 세션 연결이 종료되어도 지속적으로 동작할 수 있다.
# 리다이렉션
nohup 명령어의 특징 중 하나는 표준 출력(standard output)을 nohup.out으로 리다이렉션(redirection)한다.
터미널이 종료되어도 표준 출력은 계속 nohup.out에 기록하기 때문에 프로세스의 상태를 알기는 좋다.
하지만 계속 nohup.out에 로그를 쌓게 될 경우 디스크 낭비로 이어지기 때문에 꼭 필요한 로그가 아니라면
로그가 남지 않도록 nohup.out 파일이 생성되지 않도록 리다이렉션을 설정하는 것이 중요하다.
# nohup 실행
nohup 명령어를 통해 프로세스를 실행하는 방법은 아래와 같다.
nohup [프로세스] &
보통 백그라운드에서 실행시키기 때문에 &를 사용한다.
# nohup.out 파일 생성하지 않는 방법
nohup.out 파일을 생성하지 않으려면 표준 출력과 표준 에러를 /dev/null로 리다이렉션해주면 된다.
nohup [프로세스] 1>/dev/null 2>&1 &
1>/dev/null은 표준 출력을 /dev/null로 리다이렉션하겠다는 뜻이고, 2>&1은 표준 에러를 표준 출력과 동일하게 하겠다는 명령어이다.
# nohup으로 실행한 프로세스 죽이기
죽여보겠다.
1. 먼저 ps -ef 로 nohup으로 생성한 프로세스의 pid를 찾는다.
2. kill 명령어를 통해 pid의 프로세스에 종료 시그널을 보내야한다.
Strategy Pattern
달라질 수 있는 것과 변하지 않는 것을 구분한다.
달라지는 부분을 찾아내서 변하지 않는 나머지 코드에 영향을 주지 않기 위해서 캡슐화시킨다.
예를 들어, 게임 상에서 화면에 나타내는 공지의 종류가 여러가지라고 가정해보자.
1. 화면 중앙에 나타나서 시작 시간만 세팅되어 있어서 10초 후 사라지는 공지A
2. 채팅 화면에 나타나서 시작 시간과 종료 시간에 맞춰서 사라지는 공지B
3. 화면 왼쪽에 나타나서 시작 시간과 종료 시간 사이에서 1시간마다 나타나는 공지C
공지이기 때문에 그림과 텍스트가 필수적이라는 점(불변)을 제외하면 나머지 요소는 달라질 수 있는 점들이다.
공지 노출 위치, 공지 노출 시간은 달라질 수 있는 점이다.
위와 같이 달라질 수 있는 점들을 '공지'라는 불변 요소로부터 분리시키기 위해 인터페이스로 분리한다.
인터페이스 Location, Time
클래스 LocationCenter, LocationChat, LocationLeft는 인터페이스 Location을 구현한다.
클래스 TimeInstant, TimeConstant, TimeRepeat는 인터페이스 Time을 구현한다.
interface Location {
public int[] present();
}
class LocationCenter implements Location {
public int[] present() {
return new int[] {centerX, centerY};
}
}
class LocationChat implements Location {
public int[] present() {
return new int[] {chatX, chatY};
}
}
class LocationLeft implements Location {
public int[] present() {
return new int[] {LeftX, LeftY};
}
}
interface Time {
public int[] present();
}
class TimeInstant implements Time {
public int[] present() {
return new int[] {hourI, minuteI, secondI};
}
}
class TimeConstant implements Time {
public int[] present() {
return new int[] {hourC, minuteC, secondC};
}
}
class TimeRepeat implements Time {
public int[] present() {
return new int[] {hourR, minuteR, secondR};
}
}
위와 같이 인터페이스로 달라질 수 있는 점을 분리하고 구현할 경우 상속에 의존하지 않게 된다.
상속에 의존하게 되었다면 공지라는 부모 클래스의 구성 요소를 바꾸고 바꿔서 공지A, 공지B, 공지C를 만들어야 했겠지만..
지금은 달라질 수 있는 부분인 공지가 나타나는 위치와 공지가 나타났다가 사라지는 시간을 '공지'로부터 분리했기 때문에
클래스 수정을 최소화할 수 있다.
class 공지 {
private Location location;
private Time time;
private String text;
private String img_url;
public void present() {
this.location.present();
this.time.present();
}
public void setLocation(Location location) {
this.location = location;
}
public void setTime(Time time) {
this.time = time;
}
}
완전 새로운 공지가 등장해도 Location과 Time을 구현하는 클래스를 생성해서 사용하게 되면 기존 '공지' 클래스를 변경하지 않고도 구성할 수 있다.
위와 같이 두 클래스를 합치는 것을 구성(composition)을 이용하는 것이라고 보면 된다.
행동을 상속받기보다는 올바른 행동 클래스로 구성된다고 보면 된다.