섹션 4. 정규 표현식과 검색도구

 

리눅스 서버를 자동화하여 관리하기 위한 쉘 스크립트(Shell Script) 심화 강좌를 정리합니다.
출처 : inflearn

1. 정규 표현식

정규 표현식(Regular Expression)과 GREP

  1. 정규표현식이란
    • 정규표현식은 다른 문자열을 검색하거나 치환할 목적으로 고안된 특수한 문자열
    • grep, sed, awk 등 가장 강력한 유닉스 명령행 도구 중 일부는 정규표현식을 사용함
  2. 정규표현식의 예
    • golf ;; 기본검색
    • [Gg]olf ;; 대괄호 [] 사용하기
  3. 정규표현식을 써볼 수 있는 사이
/a/g # a가 들어간 모든 글자
/[abcde]/g # abcde 가 들어간 모든 글자
/[a-zA-Z0-9]/g # 알파벳 대소문자, 모든숫자
/[^a-zA-Z0-9]/g # 알파벳 대소문자, 숫자를 제외한 모든 글자

/./g # 모든 글자
/.+/g # +는 어떤 글자의 반복을 나타냄
/.+ /g # 어떤 문자와 공백문자
/\w/g # /[a-zA-Z0-9]/g 와 동일

/./g # 모든 글자가 하나하나 선택됨.(공백문자 포함)
/.+/g # 모든 글자의 반복??
/\s/g # 공백 선택
/\S/g # 공백문자를 빼고 모든 글자가 선택됨.
/\W/g # 알파벳 대소문자를 제외한 문자 선택

/\d/g # 숫자만 선택됨
/\D/g # 숫자를 제외한 모든문자가 선택됨.
/\b.+?\b/g # 단어 선택
/n+/g # n이 반복되는 문자 (nn 이 있을 경우 하나로 인식)

# URL 패턴매칭
/http:\/\/www.website.com/g # 특수문자는 escape를 앞에 붙여야함.
/https?:\/\/.+/g #? 는 전글자가 있거나 없거나
/(https?:\/\/)?(www\.)?website\.com.+/g # website 가 들어간 url 선택


/#[0-9a-fA-F]+/g  #HEX 값 선택

#CSS 패턴 매칭, rgb 기호 선택
/[rR][gG][bB]a?\([0-9\.\s%]{1,6},[0-9\.\s%]{1,6},[0-9.\ %]{1,6}(,[0-9\. %]{1,6})?\)/ig

# IP 주소 패턴 매칭
/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ig

# 찾고자 하는 문자열의 맨처음 단어만 선택
/^welcome/g

# welcome 이라는 문자가 나오기 전까지 패턴 매칭(greedy mode) - 큰 범위로 매칭
/.+welcome/g

# 좁은 범위로 매칭 (lazy mode)
/.+?welcome/g

2. 정규 표현식 비교(if..fi)

linux:/home/shkim$ read -p "Are you ready ?, press any key (y/n)" -n 1
Are you ready ?, press any key (y/n)

# regex 비교문(=~) 우측창에 정규표현식 패턴을 적는다
# ^ 시작, $ 끝을 나타냄
linux:/home/shkim$ if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1; fi


  • 정규표현식 비교문을 작성할때 인용부호는 사용하지 말아야 한다.
linux:/home/shkim$ locale='(..)_(..)'

# 환경변수 LANG은 locale 정보를 담고 있다.
linux:/home/shkim$ if [[ $LANG =~ $locale ]]
> then
> echo "Your country code is ${BASH_REMATCH[2]}."
> echo "Your language code is ${BASH_REMATCH[1]}."
> fi
Your country code is US.
Your language code is en.

linux:/home/shkim$ echo $LANG
en_US.UTF-8

DRILL : 사용자가 입력한 값이 숫자인지 아닌지 판별하는 스크립트를 작성해 보세요.

#정답
read -p "Enter a number" number
pat='^[0-9]+$'
if ! [[ $number =~ $pat ]]; then
   echo "Not a number" >&2
else
   echo "a number"
fi

3. 명령어(grep)

  • Unix 시절 부터 써오던 문자열 탐색 명령어. 특정 문자열을 찾고자 할 때 사용하는 명령어.
linux:/home/shkim/shell_cmd$ cat president.txt
1. George Washington (1789-1797)    John Adams (1789-1797)
2. John Adams (1797-1801)   Thomas Jefferson (1797-1801)
3. Thomas Jefferson (1801-1809) Aaron Burr (1801-1805)
George Clinton (1805-1809)
4. James Madison (1809-1817)    George Clinton (1809-1812)
None (1812-1813)
Elbridge Gerry (1813-1814)
None (1814-1817)
5. James Monroe (1817-1825) Daniel D. Tompkins (1817-1825)
6. John Quincy Adams (1825-1829)    John C. Calhoun (1825-1829)
7. Andrew Jackson (1829-1837)   John C. Calhoun (1829-1832)
None (1832-1833)
Martin Van Buren (1833-1837)
8. Martin Van Buren (1837-1841) Richard M. Johnson (1837-1841)
9. William Henry Harrison (1841)    John Tyler (1841)
10. John Tyler (1841-1845)  None (1841-1845)
11. James K. Polk (1845-1849)   George M. Dallas (1845-1849)
12. Zachary Taylor (1849-1850)  Millard Fillmore (1849-1850)
13. Millard Fillmore (1850-1853)    None (1850-1853)
14. Franklin Pierce (1853-1857) William King (1853)
None (1853-1857)
15. James Buchanan (1857-1861)  John C. Breckinridge (1857-1861)
16. Abraham Lincoln (1861-1865) Hannibal Hamlin (1861-1865)
Andrew Johnson (1865)
17. Andrew Johnson (1865-1869)  None (1865-1869)
18. Ulysses S. Grant (1869-1877)    Schuyler Colfax (1869-1873)
Henry Wilson (1873-1875)
None (1875-1877)
19. Rutherford B. Hayes (1877-1881) William Wheeler (1877-1881)
20. James A. Garfield (1881)    Chester Arthur (1881)
21. Chester Arthur (1881-1885)  None (1881-1885)
22. Grover Cleveland (1885-1889)    Thomas Hendricks (1885)
None (1885-1889)
23. Benjamin Harrison (1889-1893)   Levi P. Morton (1889-1893)
24. Grover Cleveland (1893-1897)    Adlai E. Stevenson (1893-1897)
25. William McKinley (1897-1901)    Garret Hobart (1897-1899)
None (1899-1901)
Theodore Roosevelt (1901)
26. Theodore Roosevelt (1901-1909)  None (1901-1905)
Charles Fairbanks (1905-1909)
27. William Howard Taft (1909-1913) James S. Sherman (1909-1912)
None (1912-1913)
28. Woodrow Wilson (1913-1921)  Thomas R. Marshall (1913-1921)
29. Warren G. Harding (1921-1923)   Calvin Coolidge (1921-1923)
30. Calvin Coolidge (1923-1929) None (1923-1925)
Charles Dawes (1925-1929)
31. Herbert Hoover (1929-1933)  Charles Curtis (1929-1933)
32. Franklin D. Roosevelt (1933-1945)   John Nance Garner (1933-1941)
Henry A. Wallace (1941-1945)
Harry S. Truman (1945)
33. Harry S. Truman (1945-1953) None (1945-1949)
Alben Barkley (1949-1953)
34. Dwight D. Eisenhower (1953-1961)    Richard Nixon (1953-1961)
35. John F. Kennedy (1961-1963) Lyndon B. Johnson (1961-1963)
36. Lyndon B. Johnson (1963-1969)   None (1963-1965)
Hubert Humphrey (1965-1969)
37. Richard Nixon (1969-1974)   Spiro Agnew (1969-1973)
None (1973)
Gerald Ford (1973-1974)
38. Gerald Ford (1974-1977) None (1974)
Nelson Rockefeller (1974-1977)
39. Jimmy Carter (1977-1981)    Walter Mondale (1977-1981)
40. Ronald Reagan (1981-1989)   George Bush (1981-1989)
41. George Bush (1989-1993) Dan Quayle (1989-1993)
42. Bill Clinton (1993-2001)    Al Gore (1993-2001)
43. George W. Bush (2001-2009)  Dick Cheney (2001-2009)
44. Barack Obama (2009-2017)    Joe Biden (2009-2017)
45. Donald Trump (2017- )   Mike Pence (2017- )

linux:/home/shkim/shell_cmd$ grep "George.*Washington" president.txt
1. George Washington (1789-1797)    John Adams (1789-1797)

# George 라는 단어의 Count => -c
linux:/home/shkim/shell_cmd$ grep -c George president.txt
7

# 패턴 매칭이 일어난 부분부터 마지막까지 출력해주는 옵션 => -o
linux:/home/shkim/shell_cmd$ grep -o 'George.*' president.txt
George Washington (1789-1797)    John Adams (1789-1797)
George Clinton (1805-1809)
George Clinton (1809-1812)
George M. Dallas (1845-1849)
George Bush (1981-1989)
George Bush (1989-1993) Dan Quayle (1989-1993)
George W. Bush (2001-2009)  Dick Cheney (2001-2009)

# 파일내의 발견된 위치 정보(단위 바이트 옵셋)
linux:/home/shkim/shell_cmd$ grep -bo 'George.*' president.txt
3:George Washington (1789-1797)    John Adams (1789-1797)
171:George Clinton (1805-1809)
230:George Clinton (1809-1812)
741:George M. Dallas (1845-1849)
2820:George Bush (1981-1989)
2848:George Bush (1989-1993) Dan Quayle (1989-1993)
2951:George W. Bush (2001-2009)  Dick Cheney (2001-2009)

# Donald 또는 Obama
linux:/home/shkim/shell_cmd$ grep 'Donald\|Obama' president.txt
44. Barack Obama (2009-2017)    Joe Biden (2009-2017)
45. Donald Trump (2017- )   Mike Pence (2017- )

linux:/home/shkim/shell_cmd$ egrep 'Donald|Obama' president.txt
44. Barack Obama (2009-2017)    Joe Biden (2009-2017)
45. Donald Trump (2017- )   Mike Pence (2017- )

# POSIX 패턴 => [[:digit:]]
linux:/home/shkim/shell_cmd$ grep -E '^\d{1,2}\. George' president.txt
linux:/home/shkim/shell_cmd$ grep -E '^[[:digit:]]{1,2}\. George' president.txt
1. George Washington (1789-1797)    John Adams (1789-1797)
41. George Bush (1989-1993) Dan Quayle (1989-1993)
43. George W. Bush (2001-2009)  Dick Cheney (2001-2009)

# 대소문자 구분하지 않고 검색 => -i
linux:/home/shkim/shell_cmd$ grep -i 'richard' president.txt
8. Martin Van Buren (1837-1841) Richard M. Johnson (1837-1841)
34. Dwight D. Eisenhower (1953-1961)    Richard Nixon (1953-1961)
37. Richard Nixon (1969-1974)   Spiro Agnew (1969-1973)

# 지정한 패턴을 제외하는 옵션 => -v
linux:/home/shkim/shell_cmd$ grep -i 'richard' president.txt | grep -v 'Nixon'
8. Martin Van Buren (1837-1841) Richard M. Johnson (1837-1841)

# 빈줄을 찾아주는 옵션 => ^$
linux:/home/shkim/shell_cmd$ grep -c "^$" president.txt
0 # 빈줄없음

# 1980년대 해당하는 것은 모두 찾아라. $ 맨 마지막 패턴 매칭
linux:/home/shkim/shell_cmd$ grep -E '198[[:digit:]]\)$' president.txt
39. Jimmy Carter (1977-1981)    Walter Mondale (1977-1981)
40. Ronald Reagan (1981-1989)   George Bush (1981-1989)

# -A1 : after , -B1 : before // 198이 들어가는 line의 앞의 한줄, 뒤의 한줄 출력
linux:/home/shkim/shell_cmd$ grep -A1 -B1 -E '198[[:digit:]]\)$' president.txt
Nelson Rockefeller (1974-1977)
39. Jimmy Carter (1977-1981)    Walter Mondale (1977-1981)
40. Ronald Reagan (1981-1989)   George Bush (1981-1989)
41. George Bush (1989-1993) Dan Quayle (1989-1993)

# 첫글자가 숫자가 아니면서, 라인 처음에 발견되는 문자열 출력
linux:/home/shkim/shell_cmd$ egrep '^[^0-9]' president.txt
George Clinton (1805-1809)
None (1812-1813)
Elbridge Gerry (1813-1814)
None (1814-1817)
None (1832-1833)
Martin Van Buren (1833-1837)
None (1853-1857)
Andrew Johnson (1865)
Henry Wilson (1873-1875)
None (1875-1877)
None (1885-1889)
None (1899-1901)
Theodore Roosevelt (1901)
Charles Fairbanks (1905-1909)
None (1912-1913)
Charles Dawes (1925-1929)
Henry A. Wallace (1941-1945)
Harry S. Truman (1945)
Alben Barkley (1949-1953)
Hubert Humphrey (1965-1969)
None (1973)
Gerald Ford (1973-1974)
Nelson Rockefeller (1974-1977)

linux:/home/shkim/shell_cmd$ ifconfig eth0 | grep 'inet' | cut -d: -f2 | awk '{print $2}'
172.16.1.166

# 일반 검색용 찾기(find)명령
linux:/home/shkim/shell_cmd$ fgrep '(1974' president.txt
38. Gerald Ford (1974-1977) None (1974)
Nelson Rockefeller
linux:/home/shkim/a$ seq 1 100 > nums
linux:/home/shkim/a$ cat nums
1
2
3
4
5
.
.
99
100
linux:/home/shkim/a$ sed -n '15,18p' nums
15
16
17
18
# George 라는 단어를 Tom 으로 변경
linux:/home/shkim/a$ sed 's/George/Tom/' president.txt | grep -n Tom
1:1. Tom Washington (1789-1797)    John Adams (1789-1797)
4:Tom Clinton (1805-1809)
5:4. James Madison (1809-1817)    Tom Clinton (1809-1812)
9:5. James Monroe (1817-1825) Daniel D. Tompkins (1817-1825)
17:11. James K. Polk (1845-1849)   Tom M. Dallas (1845-1849)
63:40. Ronald Reagan (1981-1989)   Tom Bush (1981-1989)
64:41. Tom Bush (1989-1993) Dan Quayle (1989-1993)
66:43. Tom W. Bush (2001-2009)  Dick Cheney (2001-2009)
linux:/home/shkim/a$ sed 's/1981/2981/' president.txt | grep -n 2981
62:39. Jimmy Carter (1977-2981)    Walter Mondale (1977-1981)
63:40. Ronald Reagan (2981-1989)   George Bush (1981-1989)

# g 모두 변경
linux:/home/shkim/a$ sed 's/1981/2981/g' president.txt | grep -n 2981
62:39. Jimmy Carter (1977-2981)    Walter Mondale (1977-2981)
63:40. Ronald Reagan (2981-1989)   George Bush (2981-1989)
# -E 확장된 정규표현식을 지원해준는 옵션
linux:/home/shkim/a$ sed -E '/\(/s/\(.*//g' president.txt | more
1. George Washington 
2. John Adams 
3. Thomas Jefferson 
George Clinton 
4. James Madison 
None 
...
linux:/home/shkim/a$ sed 's/.$//' president.txt 
1. George Washington (1789-1797)    John Adams (1789-1797
2. John Adams (1797-1801)   Thomas Jefferson (1797-1801
3. Thomas Jefferson (1801-1809) Aaron Burr (1801-1805
George Clinton (1805-1809
4. James Madison (1809-1817)    George Clinton (1809-1812
...
linux:/home/shkim/a$ SP=$' ';TAB=$'\t';sed -E 's/'"(${SP}|${TAB})"'{2,5}.+$//' president.txt
1. George Washington (1789-1797)
2. John Adams (1797-1801)
3. Thomas Jefferson (1801-1809) Aaron Burr (1801-1805)
George Clinton (1805-1809)
4. James Madison (1809-1817)
None (1812-1813)
Elbridge Gerry (1813-1814)
None (1814-1817)
...
linux:/home/shkim/a$ echo -en msdos says "\"hello world\"\x0a\x0d" > msdos.txt
linux:/home/shkim/a$ cat msdos.txt 
msdos says "hello world"

# sed 명령을 통해 msdos 형식의 파일을 linux 형식으로 변경할 수 있다.
linux:/home/shkim/a$ hexdump -C msdos.txt 
00000000  6d 73 64 6f 73 20 73 61  79 73 20 22 68 65 6c 6c  |msdos says "hell|
00000010  6f 20 77 6f 72 6c 64 22  0a 0d                    |o world"..|
0000001a

# 마지막 글자를 지우는 명령어.
linux:/home/shkim/a$ sed 's/.$//' msdos.txt 
msdos says "hello world
linux:/home/shkim/a$ hexdump -C msdos.txt > msdos_modified.txt
linux:/home/shkim/a$ hexdump -C msdos.txt 
00000000  6d 73 64 6f 73 20 73 61  79 73 20 22 68 65 6c 6c  |msdos says "hell|
00000010  6f 20 77 6f 72 6c 64 22  0a 0d                    |o world"..|
0000001a
# 첫번째 필드만 출력 콜론이 나올때 까지.
linux:/home/shkim/a$ sed -E 's/([^:]*).*/\1/g' /etc/passwd
root
bin
daemon
adm
lp
sync
shutdown
halt
...
# 히어스트링 : <<< 첫글자 대문자로 변환
linux:/home/shkim/a$ sed -E "s/\b(.)/\u\1/g" <<< "hello world"
Hello World

linux:/home/shkim/a$ sed -E "s/\b(.)/\l\1/g" <<< "HELLO WORLD"
hELLO wORLD

4. 중괄호 확장

linux:/home/shkim/a$ touch noname{1,2,3,4,5,6,7,8,9}
linux:/home/shkim/a$ ls
noname1  noname2  noname3  noname4  noname5  noname6  noname7  noname8  noname9

linux:/home/shkim/a$ mkdir dir{1..9}
linux:/home/shkim/a$ ls
dir1  dir2  dir3  dir4  dir5  dir6  dir7  dir8  dir9
linux:/home/shkim/a$ mkdir -p newproject/{lib,doc/{pdf,ppt,doc},src,include/sys,bin}
linux:/home/shkim/a$ tree
.
└── newproject
    ├── bin
    ├── doc
    │   ├── doc
    │   ├── pdf
    │   └── ppt
    ├── include
    │   └── sys
    ├── lib
    └── src

10 directories, 0 files
# eval을 사용하면 입력라인이 두번 구문 분석됩니다.
# 코드를 동적으로 평가하는데 사용됩니다.
linux:/home/shkim/a$ length=40
linux:/home/shkim/a$ eval printf -v line '%.0s-' {1..$length}
linux:/home/shkim/a$ echo $line
----------------------------------------
linux:/home/shkim/a$ printf -v line '%.0s-' {1..$length}
linux:/home/shkim/a$ echo $line
-
linux:/home/shkim/a$ cp president.txt{,.bak}
linux:/home/shkim/a$ ls
president.txt  president.txt.bak

5. 명령 대체

linux:/home/shkim$ LANG=C; date
Tue Apr  6 22:10:38 KST 2021

# 가급적이면 가독성을 높이기 위해서 $() 형태를 사용하는 것이 좋다.
linux:/home/shkim$ LANG=C; echo "today is $(date)"
today is Tue Apr  6 22:11:47 KST 2021

linux:/home/shkim$ LANG=C; echo "today is `date`"
today is Tue Apr  6 22:13:10 KST 2021
# cd dir1 이 실패하면 우측 mkdir dir1으로 디렉토리를 만들고 dir1 으로 이동 후 현재경로 확인
linux:/home/shkim$ export DIR1="$( cd dir1 || { mkdir dir1 && cd dir1;}; pwd )"
linux:/home/shkim$ echo $DIR1
/home/shkim/dir1

6. 산술확장

linux:/home/shkim$ v1=12
linux:/home/shkim$ v2=$(($v1+34))
linux:/home/shkim$ echo $v2
46
linux:/home/shkim$ v3=$(($v1+$v2)); echo $v3
58
# 산술확장 내부에서는 변수명에 달러기호 $를 생략할 수 있다.
linux:/home/shkim$ v3=$((v1+v2)); echo $v3
58
linux:/home/shkim$ ((v3++))
linux:/home/shkim$ echo $v3
59
linux:/home/shkim$ ((v3+1)); echo $v3 # c언어 복합대입연산자
linux:/home/shkim$ if true; then echo true; fi
true
linux:/home/shkim$ if ((true));then echo true; fi
linux:/home/shkim$ if ((0)); then echo true; fi # ShellScript에서 0은 거짓 1은 참
linux:/home/shkim$ if ((1)); then echo true; fi
true
linux:/home/shkim$ if $((true)); then echo true; fi
0: command not found
linux:/home/shkim$

7. exit와 종료상태

linux:/home/shkim$ rm myfile; echo $?
rm: cannot remove 'myfile': No such file or directory
1
linux:/home/shkim$ rm myfile || { echo 'Could not delete file!' } >&2; exit 1;}
rm: cannot remove 'myfile': No such file or directory
Could not delete file!
logout
Connection to 192.168.16.213 closed.
exit 0 과 exit 1의 차이점
쉘 종료시 오류의 발생여부 차이로 해석하게 됨. exit 1 => 실패 종료

8. 논리 연산 || 와 &&

# commmand1 || command2
# command1 이 성공적으로 실행되지 않은 경우에만 command2를 실행합니다.
linux:/home/shkim$ ls dir || { mkdir dir; }
# commmand1 && command2
# command1 이 성공적으로 실행된 경우에만 command2를 실행합니다.
linux:/home/shkim/dir$ cd mdir && ./myscript

9. 로그인쉘과 비 로그인 쉘

linux:/root$ shopt -q login_shell && echo 'login' || echo 'not-login'
login
linux:/root$ 

10. 명령분리자

linux:/home/shkim$ if true; then echo true; fi
true
linux:/home/shkim$ if true
> then echo true
> fi
true
linux:/home/shkim$ tool=saw; echo $tool
saw
linux:/home/shkim$ tool=hammer echo $tool # 명령 분리자 ; 가 생략되어 적용되지 않음.
saw
linux:/home/shkim$ echo $tool
saw

11. 인라인 그룹

linux:/home/shkim/test$ ls dir3 && echo "dir directory is here" || mkdir dir3 && echo "dir directory is made"
ls: cannot access dir3: No such file or directory
dir directory is made
linux:/home/shkim/test$ ls
dir3
linux:/home/shkim/test$ ls dir3 && echo "dir directory is here" || { mkdir dir3 && echo "dir directory is made";}
dir directory is here
linux:/home/shkim/test$ rm -rf dir3/

# 인라인 그룹 사용 방법 { ;} // 마지막에 세미콜론 유의
linux:/home/shkim/test$ ls dir3 && echo "dir directory is here" || { mkdir dir3 && echo "dir directory is made";}
ls: cannot access dir3: No such file or directory
dir directory is made
linux:/home/shkim/test$ ls
dir3