NullPointerException을 예방하는 방법

Posted by 사용자 귝
2018. 2. 22. 11:00 Java

NullPointerException은 null 때문에 발생하는 Runtime Exception입니다.

null인 객체에 접근하여 의도치 않게 NPE가 발생할 수 있지만 더 중요한 문제는 null 자체의 의미가 모호해 다양한 버그를 만들어냅니다. 예를 들어 메소드의 호출 결과로 null이 반환 되었을 경우 데이터가 없음을 의미할 수도 있지만 실패를 의미할 수도 있습니다. 심지어 성공을 의미하는 경우도 있을 수 있죠.

1
2
3
4
Map<String, String> map = new HashMap<String, String>();
map.put("hello", null);
map.get("hello");   // "hello" key의 value인 null을 return
map.get("nice");    // "nice" key가 없으므로 null을 return

위의 경우도 key에 대한 value가 null인지, key가 없어서 null을 return 한건지 return 받은 null 값을 가지고는 그 의미가 모호합니다. 누가 null을 만들었는지 참 얄밉지만 어쨋든 null 체크를 제대로 안했던지, 설계상의 결함이던지 null로 인한 버그는 결국 개발자 책임입니다.

똥이 무서워서 피하기보단 더러워서 피한다고… NPE를 예방하는 방법에 대해 정리해봤습니다.
들어가기에 앞서 null에 대한 프로그래머 대가들의 한마디…

“Null Sucks!”
– Doug Lea : Concurrent Programming In Java 저자 & JDK의 concurrency utilities 개발자

“I call it my billion-dollar mistake.”
– C.A.R. Hoare 2009년 어느 컨퍼런스에서.
1965년 Algol W 언어에서 처음으로 null reference를 만든 장본인 (Quick Sort도 발명)

코딩 습관 들이기

객체가 언제든 null일 수 있다는 판단하에 접근하면 도움이 될 것 같습니다.

1. equals 메소드 사용시
문자열 비교시 non-null String 기준으로 비교합니다. equals 메소드는 symmetric하므로 a.equals(b)와 b.equals(a)가 동일합니다. 그렇다면 null 일 수 있는 객체에서 equals 메소드 호출은 피하는게 낫겠죠?

1
2
3
4
5
6
public void doSomething() {
   // name이 null일 경우, NPE 발생!
   if (name.equals("BAD")) {
      // do something
   }
}
1
2
3
4
5
6
public void doSomething() {
   // name이 null이어도 NPE 발생 안함
   if ("BAD".equals(name)) {
      // do something
   }
}

다만 equals를 통과한 null 객체가 if문 안에서 메소드 호출 등의 이유로 다시 사용된다면 동일한 NPE가 발생할 수 있습니다. 이럴 경우엔 미리 null 체크를 하는게 좋겠죠?

2. toString()보다는 valueOf()를 사용할 것
1번 처럼 null 일 수 있는 객체에서 메소드 호출은 NPE 발생 위험이 있겠죠? static으로 제공되는 valueOf()를 사용하면 null을 Parameter로 넘겨도 null을 return할 뿐 NPE는 발생하지 않습니다. (valueOf()는 String, Boxed Primitives 클래스(Integer, Double 등)에서 static 메소드로 제공 됨)

1
2
3
BigDecimal bd = getPrice();
System.out.println(String.valueOf(bd)); // NPE 발생안함
System.out.println(bd.toString()); // NPE 발생

3. 메소드에서 null return하지 않기
가장 간단한 방법은 메소드 구현시 의미없는 null을 return하지 않도록 처리합니다. null이 아닌 빈 문자열 또는 빈 콜렉션을 return 하도록 합니다.

1
2
3
4
5
6
7
8
9
10
11
public List<User> getUsers() {
   // ...
 
   Result result = executeNamedQuery(GET_ALL_USERS);
 
   if (!result.isEmpty()) {
      return result;
   }
 
   return Collections.EMPTY_LIST; // or EMPTY_SET or EMPTY_MAP, etc. Depending on your return type
}

4. null을 Parameter로 넘기지 말 것
null을 Parameter로 넘길 경우 받는 쪽에서도 어찌보면 불필요한 null 체크가 필요하겠죠? null 자체에 어떤 특별한 의미가 없다면 주지도 받지도 맙시다.

5. 불필요한 autoboxing, unautoboxing 피하기 & Object 보다는 기본형 사용하기
Boxed Primitives 클래스와 기본형(primitive) 타입간에 자동으로 autoboxing, unboxing을 해주는데요. 자동으로 기본형 변환을 해주기 때문에 헷갈리기 싶습니다. (그냥 기본값이 들어가겠거니…) 가능하면 null reference를 가질 수 있는 객체가 아닌 자바 기본형(Primitive)을 이용하는 것도 방법입니다.

1
2
3
Person ram = new Person("ram");
// getPhone()에서 return된 결과가 null일 경우 NPE 발생
int phone = ram.getPhone();
1
2
3
4
5
6
private static Integer count;
 
// NPE 발생
if( count <= 0) {
  System.out.println("Count is not started yet");
}

6. Chaining 메소드 호출 자제하기

1
String city = getPerson(id).getAddress().getCity();

중간에 return 받은 값이 null일 경우 NPE가 발생하며 Stack Trace에서도 해당 line 위치만 출력되기 때문에 어디서 에러가 발생했는지 디버깅하기도 어렵습니다.

Library 이용하기

Apache Commons lang, Google Guava(예전 Goolge Collections) 등 null safe한 method를 이용하는 방법입니다. 자세한 사용은 Library의 JavaDoc을 읽어보길 권합니다.

1. Apache Commons의 StringUtils
String의 null 체크를 간단히 할 때 많이 사용하는 클래스입니다. StringUtils.isNotEmpty(), isBlank(), isNumeric(), isWhiteSpace() 등이 있죠.

1
2
3
4
5
6
7
8
9
10
11
// StringUtils methods are null safe, they don't throw NullPointerException
System.out.println(StringUtils.isEmpty(null));
System.out.println(StringUtils.isBlank(null));
System.out.println(StringUtils.isNumeric(null));
System.out.println(StringUtils.isAllUpperCase(null));
 
Output:
true
true
false
false

2. Guava의 Optional 클래스 이용하기
Optional는 nullable한 T를 non-null 값으로 대체시키기 위한 방법인데요. (말이 어렵죠?)
Optional 객체는 non-null인 T reference를 포함하거나 아무것도 포함하고 있지 않습니다.

한마디로 Optional 객체는 명시적으로 null 값을 갖지 않는다는거죠.
NullObject Pattern을 일반화시켰다고나 할까요? 바로 이 점을 이용해서 NPE를 예방합니다.

– absent : 아무 것도 포함하고 있지 않은 상태
– present : non-null 값을 갖은 상태

1) Optional 객체의 생성 (static 메소드)

Optional.of(T)
– T로 받은 non-null 값을 포함하는 Optional 객체 반환, T가 null 일 경우 NPE 발생
Optional.absent()
– 아무것도 포함하고 있지 않는 absent Optional 객체 반환
Optional.fromNullable(T)
– T로 받은 값이 non-null일 경우 present로, null일 경우 absent로 처리한 Optional 객체 반환

2) Optional 객체를 다루기 위한 메소드

boolean isPresent()
– Optional 객체가 non-null 인스턴스를 포함할 경우 true 반환
T get()
– Optional 객체가 present 일 경우 포함하고 있는 인스턴스를 반환, absent일 경우 IllegalStateException 발생
T or(T)
– Optional 객체의 present 값을 반환. 만일 값이 없을 경우 명시한 T를 반환 (기본값)
T orNull()
– Optional 객체의 present 값을 반환, 만일 값이 없을 경우 null을 반환. fromNullable의 역임
Set asSet()
– Optional 객체에서 포함하고 있는 인스턴스의 변경 불가능한 싱글톤 Set을 반환. 만일 인스턴스가 없다면 변경불가능한 Empty set을 반환

3) 간단 예제

1
2
3
4
5
6
7
Optional<Integer> possible = Optional.of(5);
System.out.println(possible.isPresent());
System.out.println(possible.get());
 
Output:
true
5

4) 응용 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Integer a = 10;
Integer b = null;
 
Optional present = Optional.fromNullable(a);
Optional absent = Optional.fromNullable(b);
 
System.out.println("a is present : " + present.isPresent());
System.out.println("b is present : " + absent.isPresent());
System.out.println("a value : " + present.or(0));
System.out.println("b value : " + absent.or(0));
 
Output:
a is present : true
b is present : false
a value : 10
b value : 0

http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html
https://code.google.com/p/guava-libraries/wiki/UsingAndAvoidingNullExplained#Optional

요건 Wrapper 메소드 형식으로 Optional 객체를 반환하게끔 사용하면 좋을 것 같네요.

3. Guava의 Preconditions 또는 NullPointerTester 클래스 이용하기
http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/base/Preconditions.html#checkNotNull(T)
http://guava-libraries.googlecode.com/svn-history/r144/trunk/javadoc/com/google/common/testing/NullPointerTester.html

Nullness Annotation 활용하기

메소드의 return값 또는 파라미터의 null 허용여부를 Annotation을 이용하여 지정합니다. 코딩 습관 들이기 3, 4번 항목을 제약하기 위해 사용하는데요.

IDE에서 지원하는 @Nullable, @NotNull 등의 Annotation을 사용하면 코드에서 NPE 발생 가능여부를 미리 경고해줍니다. 또 해당 Annotation 제약사항을 위반했을 경우 컴파일시 NPE가 아닌 IllegalArgumentException 또는 IllegalStateException이 발생됩니다. Argument로 NotNull이어야 한다고 정했는데 Null이 들어왔다면 의미상 NPE보다는 잘못된 Argument가 맞겠죠?

현재 ItelliJ, Eclipse IDE 및 Find Bugs에서는 서로 다른 Annotation 라이브러리를 사용하는데요. JCP에 Software Defect Detection하기 위한 Annotation이 JSR-305로 요청 중이나 현재 dormant(중단) 상태네요. 언제 JDK에 포함될런지는 모르겠네요 :'(

상세 사용법은 아래를 참고하세요.

1. Eclipse (Juno 이상)
http://help.eclipse.org/juno/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-using_null_annotations.htm

2. IntelliJ
http://www.jetbrains.com/idea/documentation/howto.html

3. FindBugs
http://findbugs.sourceforge.net/manual/annotations.html

4. IntelliJ 예제
– 프로젝트에 /REDIST/annotations.jar 추가
– Project Settings > Inspections > Probable bugs 체크 > Constant conditions & exception 체크 > Sueggest @Nullable annotation …. 체크

1
2
3
4
5
6
7
8
9
10
11
12
@Nullable
private String getDefinition() {
   // ...
}
 
public void doSomething() {
   if (getDefinition().equals("DEFINITION")) {
      // ...
   }
 
   // ...
}

@Nullable인 getDefinition()는 NPE가 발생할 수 있다.’고 IntelliJ에서 경고합니다.

1
2
3
4
5
6
7
8
9
10
private User findUser(@NotNull final String id) {
   // ...
}
 
public void doSomething() {
   // try invoking with null parameter
   User user = findUser(null);
 
   // ...
}

@NotNull 사용한 곳에서 null 값을 전달하고 있다고 IntelliJ에서 경고합니다.

기타

1. DB 테이블 컬럼 생성시 default값 설정하기
사용자로부터 입력 받은 값 중 null을 허용하더라도 default값을 설정했다면,
추후 해당 테이블에서 값을 불러와 사용할 경우 null을 예방할 수 있겠죠.

2. Null Object Pattern, Factory Pattern 사용하기

3. Spring MVC에서 지원되는 Bean Validation에서 @NotNull 사용하기 (JSR-303)

결론

1. 개발자가 코딩시 주의하여 NPE를 발생시키는 코딩 습관은 자제한다.
2. 간단한 Apache Commons나 Guava Library의 null safe 관련 클래스를 학습한 후 적용한다.
3. IDE에서 지원하는 Annotation 등을 이용해서 NPE 발생 부분은 미리 캐치하여 예방한다.

# 참고 사이트
– http://javarevisited.blogspot.kr/2013/05/ava-tips-and-best-practices-to-avoid-nullpointerexception-program-application.html
– http://howtodoinjava.com/2013/04/05/how-to-effectively-handle-nullpointerexception-in-java/
– http://isagoksu.com/2009/development/java/how-to-avoid-nullpointerexceptions-npe/
– https://code.google.com/p/guava-libraries/wiki/UsingAndAvoidingNullExplained
– http://www.scottlogic.com/blog/2013/09/09/nullable-in-kepler.html

'Java' 카테고리의 다른 글

NullPointerException을 예방하는 방법  (0) 2018.02.22
JAVA 강좌  (0) 2015.10.16
이 댓글을 비밀 댓글로

꼭 필요한 리눅스 명령어

Posted by 사용자 귝
2017. 5. 9. 09:52 DB

여러 프로젝트를 진행하다보면, 리눅스 또는 유닉스를 다룰수 밖에 없는 상황이 오게 마련입니다. 그러한 상황에 직면했을때 매번 웹 검색을 하거나 그나마 몇가지 알고 있는 명령어만 가지고 반복작업을 통해 해결하곤 합니다. 그 간의 경험을 바탕으로 알고나면 단순한 반복 작업을 간단하게 해결 할 수 있는 몇가지 팁을 소개합니다.

이 글은 현재 프로젝트에서 사용중인 CentOS Linux 기준으로 작성이 되었지만, 대부분의 리눅스에서 사용되어지는 bash셸(Bourne Again Shell) 및 공통 유틸리티에 대해서 다루므로 특정 리눅스 배포판에 관계없이 대부분 사용할 수 있습니다.

대부분의 리눅스관련 서적과 같이 명령어를 정리하자면, 글이 장황해지고 포스팅으로써 분량이 맞지 않기 때문에 커맨드라인을 효과적으로 사용 할 수 있는 유틸리티를 중심으로 소개합니다.

1. 명령어: cd

cd -

위와 같이 'cd -' 를 사용하여 이전 디렉토리로 전환을 쉽게 할수 있다.

하지만 이것의 문제점은 바로 이전의 위치만 가능하다는데 있다. 만약에 현재 위치를 기억해두고, 다른 여러디렉토리를 이동후 기억된 위치로 되돌아가고자 할때 유용하게 사용할수 있는 것이 있다.

pushd, popd

위와 같이 'pushd' 명령어를 사용하여 현재 디렉토리 위치를 저장하고 다른디렉토리로 이동하게 된다. 그 이후 여러다른 디렉토리로 이동후에 언제라도 'popd'를 사용하여 이전에 기억해둔 곳으로 전환할 수 있다.

2. 명령어: history

!!

쌍느낌표(Double exclamation) '!!' 는 바로 직전 명령어를 실행한다. 다음 예제를 보자.

!! | grep Linux

이 기능을 다음과 같이 쉽게 응용 할 수 있다. 이전 명령어에 쉽게 추가하기

sudo !!

다음과 같이 root 권한 명령어인데 'sudo' 없이 사용한 명령어를 쉽게 불러와 사용할수 있다. 이는 방향키를 올려서 이전 명령어를 불러 오고 다시 앞쪽으로 이동하여 에디트하는 번거로움을 피할수 있다.

echo !! > script.sh

가끔 출력 결과를 파일이나 스크립트로 저장할 필요가 있을때가 있다. 이런 경우 '!!'를 사용하여 반복을 피할 수 있다.

지금까지 '!!'에 대해서 알아보았다. 하지만 '!'의 기능은 조금 다른데, 이는 명령어 이력번호를 호출하는데 사용된다. 다음 예제를 보자.

!이력번호

history

이 명령어를 치면 아래와 같이 이전 명령어들이 각각 고유 식별번호가 존재하는데, 그 식별번호를 사용하여 재 사용이 가능하다. 다음 예제를 보자.

!1093

이 명령어는 1093번 명령어를 재실행한다.

!-이력상대순번

마지막 명령어로 부터 뒤로 두번째 명령어를 재호출할때 !-2 이렇게 사용하면 된다.

이전 명령어의 argument로 명령어 실행

사실 이건 알아도 자주 안써서 기억 할 수 없기 때문에 거 의 사용 하지 못한다..

Linux1_10

그 외 다음과 같다.

  • !^ 이전 명령어의 첫번째 argument이다.
  • !$ 이전 명령어의 마지막 argument이다.
  • !* 이전 명령어의 전체 argument가 있다.

다음과 같이 응용도 가능하다, 바로 이전의 인수가 아닌 특정 명령어의 인수를 사용할 수 있다.

$ ls !ls:2

'![keyword]' 이전 명령어의 keyword로 명령어 실행

Linux1_11

!ls ls로 시작되는 명령어중 가장 최근 명령어를 찾아서 재 실행한다. (IBM AIX 등의 korn셸에서는 r ls )

vi 모드

커맨드 라인 인터페이스에서 입력 모드를 vi 모드를 사용하면, 이전 명령어를 검색하여 좀 더 효율적인 히스토리 기능을 사용할 수 있다.

다음처럼 설정하여, 커맨드라인 에디트를 vi로 사용한다. 기본적으로 emacs로 설정되어 있다. ($HOME/.bash_profile 에 설정하면, 다음 로그인시에 기본으로 설정이 된다.)

$ set -o vi 
$set -o 를 입력하면 전체 커맨드 라인 옵션이 나오는데 vi 모드가 on 되어 있는지 확인하자.

Linux1_33

다음은 vi 모드에서 커맨드 라인 사용 방법이다. (vi에 익숙한 사용자는 바로 알겠지만, vi 에디터를 모르더라도 다음 부분만 익히면 된다.)

ESC 키를 쳐서 명령어 모드(ex mode)로 진입한다. (vi 에디터와 동일) 
$ /uname <엔터>

이후부터, n 키를 치면서 다음 명령어를 순차적으로 검색할 수 있다.

원하는 명령어를 찾고 나서 수정을 원하는 단어까지 w 키를치고 커서를 빠르게 이동하자

단어까지 이동하였으면 cw 입력하여 단어 편집 상태로 진입하여 타이핑하면 빠르게 커맨드 라인 입력을 할 수 있다.

(사실 저는 거의 vi모드로 대부분 커맨드 명령어를 처리합니다. )

Control-R

컨트롤키와 r 키를 동시에 입력하면, 검색어 문자 입력 대기 상태로 진입한다.

(reverse-i-search)'':

키워드를 입력할때마다 점진적으로 히스토리에서 검색한다. 다시 한번더 컨트롤 + r 키를 입력하면 순차적으로 그 이전의 명령어를 검색하여 보여준다. 원하는 명령어를 찾으면 바로 엔터를 입력 또는 명령어를 수정할때는 왼쪽 또는 오른쪽 방향기로 이동 후 수정한다.

Linux1_34

이상 히스토리 기능에 대해서 살펴 보았습니다. 이것 외에도 이전명령어 치환하는 기능 등이 있으나 개인적으로도 이정도 기능외는 잘 사용하지 않을뿐더러 사용하지 않으면 잊어버리기 때문에 무용지물이 되버리곤 합니다. 이상의 기능만 잘 활용하면 커맨드 라인에서 키보드 타이핑을 하는 고통을 일부나마 줄일수 있을것이라 생각합니다.

3. 특수문자 { }

커맨드 라인 인터페이스에서 콤마는 파일명을 나열하고 명령에게 인수로 전달한다.

touch 유틸리티는 파일의 접근, 수정 시간을 현재 시간이나 사용자가 명시한 시간으로 바꾼다. 파일이 존재하지 않을 경우에는 빈 파일을 생성한다.

Linux1_12

mkdir 유틸리티는 디렉토리 생성한다.

Linux1_13

다음과 같이 파일명 인수가 콤마 갯수 만큼 확장하고 명령어에게 전달하고 있음을 확인 할 수 있다.

echo 유틸리티는 셸이 전달하는 인수를 출력한다.

Linux1_14

4. 특수문자 ?

물음표 기호(?)는 셸에게 파일명을 생성케 하는 특수 문자다. 이 문자는 기존의 파일명에 있는 문자를 하나에 해당된다.

Linux1_15

5. 특수문자 *

별표(*)는 물음표와 비슷한 기능을 수행하지만, 어떤 개수의 문자열에라도 대응될 수 있다는 점이 다르다.

Linux1_16

6. 특수문자 [ ]

문자 주위를 둘러싼 괄호 기호 [ ]는 셸이 각각의 문자를 포함하는 파일명을 검색하게한다.

Linux1_17

7. 백그라운드에서의 프로그램 실행

지금까지의 예제에서 명령은 모두 포그라운드에서 실행됐다. 포그라운드에서 명령을 실행하면 셸은 명령의 실행이 끝날 때까지 기다렸다가 사용자에게 프롬프트를 내보낸다. 반면 백그라운드에서 명령을 실행하면 사용자는 이명령의 실행이 끝날 때까지 기다릴 필요 없이 바로 다른 명령을 실행할 수 있다.

작업(job)은 파이프로 연결 가능한 여러 개의 명령을 가리킨다. 포그라운드 작업의 경우에는 한 윈도우나 한 화면에서 하나의 작업만 실행할 수 있다. 그러나 백그라운드 작업의 경우에는 여러 개를 실행하는 것이 가능하다. 사용자는 한번에 하나 이상의 작업을 실행함으로써 리눅스의 중요한 기능 중 하나인 멀티태스킹(multitasking) 기능을 사용할 수 있다. 실행 시간이 길면서 작업 진행에 대한 별도의 감시가 필요치 않은 명령을 실행할 때는 백그라운드 실행 기능이 매우 유용하다.

백그라운드에서 명령을 실행하려면 커맨드 라인의 마지막에서 RETURN 키를 입력하기 전에 & 기호를 입력한다. 그러면 셸은 작업에 번호를 매기고 이 작업 번호를 괄호로 둘러싸서 출력한다. 작업번호 뒤에는 PID(Process Identification) 번호가 나온다. 명령 뒤에 나오는 [1]이라는 숫자는 셸이 이 작업에 대한 작업 번호를 1을 할당했다는 것을 의미한다.

백그라운드 작업의 실행이 끝나면 다음과 같은 메시지를 볼 수 있다.

[1]+ Done program

Linux1_19

포그라운드에서 백그라운드로의 작업 이동

포그라운드 작업의 실행은 작업 보류 키, 일반적으로 CONTROL-Z 키를 입력함으로써 일시 중지할 수 있다. 셸은 프로세스를 멈추고 키보드로부터의 표준 입력을 중지시킨다. 이때 사용자가 bg 명령과 작업 번호를 입력하면 중지된 작업은 백그라운드로 옮겨서 다시 시작된다. 중지된 작업이 하나밖에 없다면 작업 번호를 입력할 필요가 없이 fg만 입력한다. 만약 백그라운드 작업이 하나 이상이라면 fg 인수로써 %번호를 입력해야한다.

Linux1_18

kill : 백그라운드 작업의 중단

인터럽트 키(보통은 CONTROL-C)로는 백그라운드에서 실행되는 프로세스를 중단할 수 없다. 이러한 경우에는 kill 명령어를 사용해야 한다. 커맨드 라인에 kill과 중단하려는 프로세스의 PID를 입력하거나 퍼센트 기호(%)와 작업 번호를 입력한다.

Linux1_20

여기서 잠깐!

kill 명령어 대해서 시그널에 대해서만 잠깐 언급을 하자면, 가끔 습관적으로 무조건 강제 종료 시그널(-9)를 날리는 분을 계신데.. Java 데몬이나 기타 서버를 종료 시킬때 무조건 강제 종료시키기 보다 서버에게 종료시 자원 정리 시간을 주어 안전하게 종료하게 하자.

  1. SIGTERM(15) 시그널 
    : 프로세스에게 보내어 DB 접속 및 각종 아직 커밋이 되지 않은 정보를 안전하게 저장할 수 있도록 기회를 준다. 프로세스가 이 시그널에도 무 반응시에만 강제 시그널를 보낸다. 
    kill 13377 ( kill -15 13377 또는 kill -TERM 13377)

  2. SIGHUP(1) 시그널 
    : 재 시작 시그널이다. ( kill -1 13377 또는 kill -HUP 13377 )

  3. SIGKILL (9) 
    : 묻지도 따지지도 않고(?) 무조건 강제 종료한다. (kill -9 13377)

8. 검색 명령

리눅스에서 명령을 입력하면 셸은 프로그램에서 사용하는 디렉토리를 검색해 처음 이름이 일치하는 프로그램을 실행한다. 이때 검색하는 디렉토리 목록을 검색 경로(search path)라고 한다. 검색 경로를 변경하는 방법은 PATH 환경변수를 변경하는 것이다. 사용자가 검색 경로를 변경하지 않으면 셸은 표준 디렉토리만 검색해 보고 검색 작업을 멈추게 된다. 물론 표준 디렉토리 외의 다른 디렉토리에도 유용한 유틸리티가 존재할 수 있다.

which / whereis: 유틸리티의 위치 검색

which 유틸리티는 유틸리티 파일의 전체 경로를 보여줌으로써 유틸리티(명령어)의 위치를 알려준다. 셸은 검색 경로를 탐색해서 처음 나오는 명령을 실행한다. which 는 이런 경우 셸이 어떤 프로그램을 실행할지를 알려준다.

which유틸리티는 명령어가 예상 외의 동작을 수행할 경우 유용하다. which를 실행 해 보면 표준이 아닌 버전의 툴을 실행하고 있는지, 혹은 자신이 원하는 툴과는 전혀 다른 툴을 실행하고 있는지를 알 수 있게 될 것이다.

다음과 같이 which ls 명령어로 확인 해 보면 alias가 먼저 실행한다는것을 알 수 있다.

참고) 역슬래쉬()를 명령어 앞에 사용하면 alias 가 아닌 실제 명령어를 실행한다. ex) \ls

Linux1_28

프로그램의 이름이 주어지면 which는 검색 경로에 있는 디렉토리를 차례대로 검색, 프로그램의 위치를 찾는다. 검색 경로에 해당 프로그램이 하나 이상 존재하는 경우에 which는 처음 나오는 명령에 대한 정보만 보여준다.

whereis 유틸리티는 표준 디렉토리(standard directory) 목록을 통해 파일을 검색하며, 검색 경로와는 독립적으로 작동한다. whereis는 사용자가 지정한 바이너리(실행)파일, 매뉴얼 페이지 및 프로그램 소스 코드의 위치 검색에 사용할 수 있는 데, 이 유틸리티는 which와 달리 모든 해당 파일에 대한 정보를 보여준다.

Linux1_29

apropos: 키워드 검색

특정 작업을 수행하는 데 필요한 명령을 알지 못한다면 apropos 유틸리티로 키워드를 검색하면 된다. (apropos를 제대로 실행하려면 makewhatis로 whatis 데이터베이스를 설정하고 주기적으로 관리해야 한다. 이 작업은 보통 cron으로 처리한다.) 이 유틸리티는 man 페이지의 맨 위에 나오는 짧은 설명줄(description line)에서 키워드를 검색해 일치하는 항목이 있는 명령만 보여준다. man 유틸리티를 -k(keyword) 옵션과 함께 사용하면 apropos를 실행한 것과 동일한 결과를 볼 수 있다.

다음 예제는 apropos를 who 키워드와 함계 호출한 결과를 보여준다. 이 키워드와 관련된 다른 유용한 유틸리도 찾아서 보여준다.

Linux1_30

locate: 파일 검색

locate유틸리티는 로컬 시스템에서 파일을 검색하는 작업을 수행한다. locate는 updatedb 유틸리티로 생성/업데이트한 locate 데이터베이스가 있어야만 정확하게 검색할 수 있따. 일반적으로 이 데이터베이스는 cron 스크립트로 하루에 한 번씩 업데이트된다.

Linux1_31

grep: 파일명 검색 또는 파일내 문자열 검색

which / whereis / apropos / locate 등은 유틸리티 검색 하는데 사용되지만, grep 유틸리티는 하나 이상의 파일에서 행 단위로 간단한 문자열이나 정규 표현식으로 표현된 pattern을 찾는다.

-r 옵션은 하위디렉토까지 모두 검색한다.

-l 옵션은 파일내의 텍스트와 패턴이 매치되는 텍스트를 출력하지 말고 매칭된 파일 목록만 출력한다.

-L 옵션은 -l 옵션과 반대로 비 매칭된 파일 목록을 출력한다.

Linux1_32

9. 접근 권한

접근 권한

파일에 접근할 수 있는 사용자 유형은 다음 3가지로 구분할 수 있다.

  • 파일의 소유자(owner)
  • 파일 소유자가 속해 있는 그룹 멤버(group)
  • 그 밖의 모든 사람(other)

각 사용자별 읽기, 쓰기, 실행 의 파일 접근 권한을 설정 할 수 있으므로 모두 9가지 방법이 있다는 것을 의미한다.

Linux1_21

ls -l 권한 보기

첫 번째 문자 하이픈(-)으로 표기되어 있다. 이 파일은 일반 파일이기 때문이다. (디렉토리는 d가 표시된다.)

그 다음에 오는 문자 9개는 각 사용자별 owner, group, other 에 읽기(r), 쓰기(w), 실행(x) 권한이 부여된다. (- 문자는 권한이 없음을 나타낸다.)

Linux1_23

접근 권한 변경은 두가지 심볼릭과 절대값으로 변경이 가능하다.

접근권한 변경: 심볼릭

심볼릭 모드: 사용자 종류

Linux1_24

심볼릭 모드: 연산자

Linux1_25

심볼릭 모드: 퍼미션

Linux1_26

파일 소유자는 어떤 사용자에게 파일 접근 권한을 줄지, 그리고 어떤 접근 권한을 줄지를 결정해서 설정할 수 있다. 파일 소유자는 chmod(change mode) 유틸리티를 사용해서 파일의 접근 권한을 변경할 수 있다.

심볼릭 접근 권한 변경 예) chmod a+rw letter

의미: 모든(a) 사용자에게 읽고 쓰는 권한(rw)을 부여(+)하라.

다음은 접근 권한의 변경 유틸리티 chmod 로 심볼릭 모드 사용 예이다.

Linux1_22

접근권한 변경: 절대값

8진수를 사용하여 접근 모드를 설정할 수 있다.

절대값 접근 권한 변경 예) chmod 755 letter

의미: 소유자는 모든 권한을 그외 그룹과 다른 사용자는 읽기와 실행 권한을 부여하라

다음은 접근 권한의 변경 유틸리티 chmod 로 절대값 모드 사용 예이다.

참고1) 셸 스크립트의 경우, 읽기와 실행 퍼미션이 있어야 실행할 수 있지만 바이너리 파일은 실행 퍼미션만 있어도 실행할 수 있다.

참고2) 디렉토리 경우 실행 퍼미션이란? 디렉토리 안으로 진입 가능 여부를 뜻한다.

Linux1_27

10. yum : 시스템을 최신 버전으로 유지하기

패키지 갱신과 설치

리눅스 초기 릴리즈에는 갱신을 관리하는 툴이 없었다. RPM 툴로 개별 소프트웨어 패키지를 설치하거나 갱신할 수 있었지만, 패키지와 그 패키지와 종속 관계를 갖는 패키지를 찾는 일은 전적으로 사용자의 몫이었다.

Terra Soft, 이 회사는 자동으로 패키지를 찾고 종속 관계를 검사하는 Yellow Dog Updater를 만들었다. 이 후 다른 배포판에 포팅되었다. Yellow dog Updater, Modified ( Yum ) 는 RedHat 또는 CentOS 리눅스의 기본 패키지 관리자 이다.

패키지 저장소 환경파일

환경파일 : /etc/yum.conf

업데이트

시스템 유틸리티이므로 루트 사용자에서 실행을 해야한다. update 옵션은 설치된 모든 패키지를 갱신한다. 설치된 패키지의 패키지 헤더를 내려받은 후 계속 진행할 것인지 묻고, 갱신된 패키지를 내려받아 설치한다.

$ yum update

패키지 설치

새로운 패키지와 그것과 종속 관계를 가지는 패키지를 설치하려면, yum install 명령어 다음에 패키지 이름을 붙인다.

$ yum install telnet

패키지 삭제

비숫한 구문을 사용하여 yum으로 패키지를 삭제 할 수 있다.

$ yum remove telnet

11. 설치하고 싶은 유틸리티 찾기

자신이 설치하고 싶은 어플리케이션에 대해서 패키지명을 모르면 설치를 할 수 없다. 또한 자신이 원하는 유틸리티가 어느 패키지에 속해 있는지 알 수 없을때, 다음과 같이 검색하여 찾아 볼 수 있다.

1. 먼저 패키지 목록 조회를 하라. 
# yum list vimtutor 
- Error: No matching Packages to list

2. 유틸리티 이름으로 검색을 하라. 
# yum search vimtutor 
- No Matches found

3. 패키지 내에 서 제공하는 파일명을 검색 하라. 
# yum provides vimtutor 
- You can use “/vimtutor” and/or “bin/vimtutor” to get that behaviour 
- No Matches found

4. 위에서 제시한 방법대로, 패키지 서브 디렉토리 까지 검색하라 
# yum provide */vimtutor 
- vim-enhanced-7.2.xxxxxxx

위와같이, 출력이 되었다면 찾기에 성공한것이다. 그럼 이것을 설치해야 하는데, 다음처럼 패키지명(버전 문자 이전까지)만 입력하면 대부분 설치가 가능하다.

$ yum install vim-enhanced

설치가 성공적으로 끝나면 vimtutor 를 실행하여 제대로 설치되었는지 확인 해보자.

5. 위 4가지 방법으로 못 찾으면, 인터넷 검색을 적극 활용하여 해결의 실마리를 찾도록 한다.

이상으로 간단하게 정리하고 마무리를 하고자 합니다. 실무에서 가장 빈번하게 쓰이는 커맨드 유틸리티를 중점적으로 알아보았고, 백그라운드 프로세스 관리와 파일 접근 권한 관리에 대해서 알아 보았습니다. 또한 명령어 검색 및 소스파일내 문자열 검색 까지 살펴 보았고요, 마지막으로 패키지 유지 관리까지 정리해 보았습니다.

제가 프로젝트에서도 이 정도 사용있으며 그렇게 큰 불편없이 잘 사용하고 있습니다. 그외 간단한 셸 스크립트 정도만 익혀두시면, 정기적인 백업 스크립트를 만들어 스케줄에 등록하여 사용 할 수도 있어 활용범위가 넓어 질것입니다. 끝까지 읽어 주셔셔 감사합니다.

참조 도서 및 사이트

Practical Guide to Linux Commands, Editors, and Shell Programming by Mark G. Sobell, Published July 1st 2005 by Prentice Hall 
20 interesting and extremely helpful Linux command line tricks


출처: Nextree

'DB' 카테고리의 다른 글

꼭 필요한 리눅스 명령어  (0) 2017.05.09
이 댓글을 비밀 댓글로

정규표현식의 규칙

Posted by 사용자 귝
2017. 5. 3. 23:31 eclipse

문자열 패턴을 표현하기 위해 정규 표현식 함수와 함께 사용되는 특수 문자들이 있다. 정규 표현식에 사용되는 이 특수 문자들의 사용법을 먼저 알아보도록 하자

패턴 표현에 사용되는 특수 문자

정규 표현식에서 패턴을 표현하기 위해 사용하는 특수 문자와 규칙은 다음과 같다.

  1. '.' 특수문자

    '.'은 어떤 문자이건 간에 임의의 한 글자를 나타낸다. 예를 들면, 패턴 a.c는 어느 한 문자의 앞 문자가 a이고 뒷 문자는 c인 모든 문자열을 포함한다. 따라서 aac나 abc, acc는 이 패턴에 일치되는 문자열이다. 그러나 .은 반드시 임의의 한 글자만을 대신하기 때문에 a로 시작해 c로 끝나는 문자열 중 두 글자뿐인 ac나 abbc는 이 패턴에 포함되지 않는다.

    사용예내용해당 문자열
    s.es와 e사이에 임의의 한 글자를 갖는 문자열과 일치한다.sae, sbe, sce, sde, …
    .cece 앞에 임의의 한 글자를 갖는 문자열과 일치한다.ace, kce, fce, …

     

  2. '*' 특수문자

    '*'는 바로 앞의 문자를 의미하며 그 문자가 없거나 하나 이상임을 나타낸다. 위의 '.'이 그 자체로 임의의 한 글자만을 의미하는 데 반해 '*'는 바로 복수 개의 문자를 나타낼 수 있다. 대신 '*' 앞에는 반드시 문자가 하나 이상 있어야 한다. 만일 "*e"와 같이 '*' 앞에 문자가 없으면 잘못된 패턴으로 인식해 에러가 발생한다.

    사용예내용해당 문자열
    s*ee 앞에 s가 없거나 하나 이상 존재하는 모든 문자열을 나타낸다.e, se, sse, ssse, …
    abc*ab 다음에 c가 없거나 하나 이상 존재하는 모든 문자열을 나타낸다.ab, abc, abcc, abccc, …
    h*imim 앞에 h가 없거나 하나 이상 존재하는 모든 문자열을 나타낸다.im, him, hhim, hhhim, …

     

  3. '+' 특수문자

    '+'는 바로 앞의 문자를 의미하여 그 문자가 하나 이상임을 나타낸다. '*'와 마찬가지로 앞에 한 문자가 있어야 하며 이 문자가 최소 하나 이상 존재하는 모든 문자열을 포함한다.

    사용예내용해당 문자열
    s+e문자 e앞에 s가 최소한 하나 이상 존재하는 모든 문자열을 나타낸다.se, sse, ssse, sssse, settle, …

     

  4. '?' 특수문자

    '?'는 바로 앞의 문자가 없거나 혹은 있음을 나타낸다. 예를 들면, s?e는 '?' 앞의 문자 s가 없거나 하나 존재하는 문자열 e나 se를 포함한다.

    사용예내용해당 문자열
    th?et와 e사이에 h가 하나 있거나 혹은 없는 문자열과 일치한다.te, the, their, lotte, …

     

  5. '^' 특수문자

    '^'는 바로 뒤의 문자열을 기준으로 이것과 동일한 문자열로 시작되는 것을 가리킨다.

    사용예내용해당 문자열
    ^TheThe로 시작하는 모든 문자열을 나타낸다.The girl is beautiful!, Theater, …
    ^a?bcbc나 abc로 시작하는 모든 문자열을 나타낸다.bc++ 3.0, abcdef), …
    ^.ee 앞에 한 글자가 존재하는 문자열로 시작하는 모든 문자열을 나타낸다.he, me, request, settle, …
    ^s.e?s와 임의의 한 문자로 시작하고 그 뒤에 문자 e가 하나 있거나 혹은 없는 문자열을 나타낸다.sa, sae, sb, sbe, …

     

  6. '$' 특수문자

    '$'는 문자열의 맨 마지막을 가리킨다.

    사용예내용해당 문자열
    a?bc$bc로 끝나는 문자열 앞에 문자 a가 없거나 하나 존재하는 문자열과 일치한다.eeabc, seebc, bc, …
    t.e$t와 임의의 한 글자, 그리고 그 다음에 e로 연결되어 끝나는 문자열과 일치한다.onthetoe, bctae, appetitte, …
    s?e+$첫 글자는 s나 e로 시작하고 하나 이상의 e로 끝나는 문자열과 일치한다.e, se, ee, eee, see, seee, …
    ^the$the로 시작해서 the로 끝나는 문자열과 일치한다. 즉, 이 경우는 문자열 자체가 the뿐인 경우에만 일치한다.the

     

  7. '[]' 특수문자

    각괄호 []는 [] 안에 있는 문자열 중에서 하나의 문자만을 의미한다. [] 안에서 범위를 지정할 때는 '-' 문자를 사용한다. 즉, [a-d]는 [abcd]와 동일하며 [0-9]는 [0123456789]와 같은 의미이다.

    사용예내용해당 문자열
    [ab]cdacd 또는 bcd를 포함하는 문자열과 일치한다.acd, tacde, "bcd", "tbcde", …
    ^[ab]cdacd 또는 bcd로 시작하는 문자열과 일치한다.acds, bcdt, acdsee32, …
    [a-z]영문 소문자 한 글자를 포함하는 문자열과 일치한다.a0c2ds, ta123cde, Student, …
    [a-zA-Z]영문 소문자나 대문자 한 글자를 포함하는 문자열과 일치한다.LINUX, 386AT, …
    [0-9]십진수 한 자를 포함하는 문자열과 일치한다.a0c2ds, ta123cde, 386, …
    ga[a-z]하나의 영문 소문자 앞에 ge를 갖는 문자열과 일치한다.LINgazUX, gazzett, …
    ^ab[ce]efabcef 또는 abdef로 시작하는 문자열과 일치한다.abcef0z, abdef386, …
    ^[a-zA-Z]영문자로 시작하는 모든 문자열과 일치한다.LINgazUX, abcef0z, …
    [a-z]+영문소문자 한 자 이상을 갖는 문자열과 일치한다.tgabcabcef, MySQL, …
    [aA][bB]ab, aB, Ab, AB를 포함하는 문자열과 일치한다.386ABIT, abcef0z, tgabcabcef, …
    ,[a-zA-Z0-9]$콤마와 하나의 영문자 또는 숫자로 끝나는 문자열과 일치한다.abdef38,6, 199,2, …

     

    만일 원하지 않는 문자를 제외한 나머지 문자를 가리킬 때에는 []안의 첫 문자로 '^'를 사용한다.

    사용예내용해당 문자열
    [^ab]cdacd와 bcd를 제외하고는 패턴 .cd와 같다. 즉, cd앞에 a나 b를 제외한 하나의 문자를 포함하는 문자열과 일치한다.ccd, scd, 018cd, tgcdcdabcef, gazcd18, …
    s[^ab]tsat와 sbt를 제외하고는 패턴 s.t와 같다. 즉, t 앞에 a나 b를 제외한 임의의 한 문자와 그 앞에 s가 있는 문자열과 일치한다.sct, sdt, tgcdsctda28, settle, …
    [^a-z]영문 소문자를 제외한 한 글자를 포함한 문자열과 일치한다.MySQL, 386sAB,IT, abcef0z, 199,2, …
    [^a-zA-Z]영문자를 제외한 한 글자를 포함하는 문자열과 일치한다.286sAB,IT, gazscd18, abcef0z, 199,2, …
    [^0-9]숫자를 제외한 한 글자를 포함하는 문자열과 일치한다.settle, gazscd18, LINUX, …

     

  8. '{}' 특수문자

    중괄호 {}는 {} 앞에 있는 문자나 문자열의 개수를 결정한다.

    사용예내용해당 문자열
    a{2}baab를 가진 문자열과 일치한다. 즉, {2}는 {}앞에 있는 문자 a의 개수가 2개임을 의미한다.aab, …
    a{2,}ba의 개수가 최소한 2개 이상인 문자열을 포함하는 문자열과 일치한다.aab, aaab, aaaab, …
    a{1,3}bb앞에 1개부터 3개까지의 a를 갖는 문자열을 포함하는 문자열과 일치한다.ab, aab, aaab, …

     

  9. '()' 특수문자

    둥근괄호 ()는 () 앞에 있는 글자들을 그룹화 한다.

    사용예내용해당 문자열
    a(bc){2}a뒤에 bc의 개수가 두 개인 문자열 abcbc를 포함하는 모든 문자열과 일치한다. [bc]가 b 또는 c중 하나를 의미하는 것에 비해 (bc)는 bc를 하나의 그룹으로 처리한다.docabcbctor, tabcbc++, …
    a(bc)*a뒤에 bc가 없거나 하나 이상인 문자열과 일치한다.sea, abcd, abcbcbcbc, …

     

  10. '|' 특수문자

    '|'는 OR 연산자이다.

    사용예내용해당 문자열
    he|shehe나 she를 포함하는 문자열과 일치한다.he is handsome, she's gone, …
    (he|she)isis 앞에 he나 she를 포함하는 문자열, 즉 heis나 sheis를 포함하는 모든 문자열과 일치한다. 
    (le|li)*ftft 앞에 le나 li가 없거나 하나 이상인 문자열과 일치한다.mlefto, lft, lelift, fclelelilefte, …
    mo(no)+mo뒤에 no가 하나 이상인 문자열과 일치한다.mono, monono, mononono, acmonoe, …

     

문자 클래스(character class)

패턴을 [a-z]나 [A-Z], [0-9]로 표현하는 대신 다음과 같이 문자 클래스(character class)로 간단하게 표현할 수도 있다.

  • [[:alpha:]] : 알파벳 문자 중 하나를 의미하며 [a-zA-Z], 또는 [a-Z]와 동일한 표현이다.
  • [[:digit:]] : 숫자 한 자를 의미하며 [0-9]와 동일한 표현이다.
  • [[:alnum:]] : 알파벳 문자나 숫자 중 하나를 의미하며 [a-zA-Z0-9]와 동일한 표현이다.
  • [[:space:]] : 공백 문자 ( )를 의미한다.

정규 표현식에서 특수 문자의 표현

지금까지 정규 표현식에서 사용한 여러 특수 문자를 특수 문자가 아닌 그 문자 자체의 의미로 사용하려면 특수 문자 앞에 역슬래시('\') 문자를 붙여 escape시켜야 한다. 정규 표현식에서 escape시켜야 하는 특수 문자로는 다음과 같은 것들이 있다.

^ . [ ] $ ( ) | * + ? { } \

 

  • \*+ : 문자 '*'이 하나 이상 포함된 모든 문자열과 일치한다. 이것은 정규 표현식에서 사용하는 특수문자 '*'를 '\*'와 같이 escape시킴으로써 문자열에서 '*'가 포함된 문자열을 찾는다.
    예) *notice***, dictsh*eionary
  • \[RE:[1-9][0-9]*\] : "["와 "RE:", 그리고 0을 제외한 하나의 숫자와 0 또는 하나 이상의 0을 포함한 숫자, 그리고 "]"를 순서대로 포함하는 모든 문자열과 일치한다. 즉, 이는 게시판에 답장 글을 올릴 때의 "[RE:12]"와 같은 패턴을 의미한다.
    예) [RE:9], [RE:21]*eionary
  • [*\/+?{}.] : "*"나 "\", "/", "+", "?", "{", "}", "."중 하나를 포함하는 모든 문자열과 일치한다. 특수문자 []안에서는 위의 경우와 달리 예외가 적용된다. 즉, []안에서는 특수 문자를 문자 자체로 표현하기 위해 해당 문자를 별도로 escape시키지 않아도 된다.
    예) [RE:21]*eionary, turboc++, ce{le}brity, hello?, ac/monoe

출처 : 오픈코드스

'eclipse' 카테고리의 다른 글

정규표현식의 규칙  (0) 2017.05.03
이클립스 워크스페이스 변경  (0) 2013.07.05
이 댓글을 비밀 댓글로