변수에 표준 오차를 저장하는 방법
예를 들어 다음과 같은 스크립트가 있다고 가정해 보겠습니다.
쓸모 없는.쉬
echo "This Is Error" 1>&2
echo "This Is Output"
그리고 또 다른 셸 스크립트가 있습니다.
또한 쓸모가 없습니다.쉬
./useless.sh | sed 's/Output/Useless/'
"This Is Error" 또는 useless.sh 의 다른 stderr를 변수로 캡처하고 싶습니다.ERROR라고 하죠.
내가 어떤 것에 stdout을 사용하고 있다는 것을 주목하세요.stdout을 계속 사용하고 싶기 때문에 stderr을 stdout으로 리디렉션하는 것은 도움이 되지 않습니다.
그래서, 기본적으로, 저는 하고 싶습니다.
./useless.sh 2> $ERROR | ...
하지만 그것은 분명히 효과가 없습니다.
저는 또한 제가 할 수 있다는 것을 압니다.
./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`
하지만 그것은 추하고 불필요합니다.
불행하게도, 여기에 답이 나타나지 않는다면, 그것이 제가 해야 할 일입니다.
다른 방법이 있기를 바랍니다.
더 좋은 생각 있는 사람?
다음과 같이 오류 파일을 캡처하는 것이 더 좋습니다.
ERROR=$(</tmp/Error)
하고 '은이것인식실셸필없습다니요행가할고하을▁'셸다▁the'를 실행할가 없습니다.cat
데이터를 가져올 수 있습니다.
더 큰 문제는 어렵습니다.저는 그것을 하는 쉬운 방법이 없다고 생각합니다.오류를 표준 출력으로 리디렉션할 수 있도록 전체 파이프라인을 하위 셸에 빌드하고 최종 표준 출력을 파일로 전송해야 합니다.
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
세미콜론이 필요합니다(본, 코른 등 고전적인 셸에서는 확실히; 아마도 바시에서도).더'{}
동봉된 명령을 통해 I/O 리디렉션을 수행합니다.기록된 바와 같이, 그것은 오류를 포착할 것입니다.sed
도 마찬가지야
경고: 공식적으로 테스트되지 않은 코드입니다. 위험을 감수하고 사용하십시오.
으로, null로 또는 stderrr을 합니다. stdout은 /dev/null입니다.$()
리디렉션된 stderr을 캡처하려면:
ERROR=$(./useless.sh 2>&1 >/dev/null)
또한 쓸모가 없습니다.쉬
이게하출파연수있다습니결할의 출력을 할 수 .useless.sh
같은 사스크팅립여와 같은 .sed
그리고 저장합니다.stderr
이름의 error
는 파프결과다전송니다로 됩니다.stdout
표시하거나 다른 명령에 연결할 수 있습니다.
이를 위해 필요한 리디렉션을 관리하기 위해 두 개의 추가 파일 설명자를 설정합니다.
#!/bin/bash
exec 3>&1 4>&2 #set up extra file descriptors
error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 )
echo "The message is \"${error}.\""
exec 3>&- 4>&- # release the extra file descriptors
이 질문에는 중복되는 내용이 많은데, 이중 상당수는 stderr 및 stdout과 종료 코드를 동시에 캡처하지 않으려는 사용 시나리오가 약간 더 단순합니다.
if result=$(useless.sh 2>&1); then
stdout=$result
else
rc=$?
stderr=$result
fi
성공한 경우 적절한 출력이 예상되거나 실패한 경우 stderr에 진단 메시지가 예상되는 일반 시나리오에서 작동합니다.
셸의제어은다이검사다니합음을미문다▁examine▁note검니▁already▁the를 검토하고 있음을 하십시오.$?
; ; 그서는것은무엇든이이래후▁like▁which든무▁under▁looks이엇;;것▁anything▁so은래.
cmd
if [ $? -eq 0 ], then ...
그저 어설프고 비식용적인 표현일 뿐입니다.
if cmd; then ...
독자들의 이익을 위해, 이 레시피는 여기에 있습니다.
- 변수에 stderr을 고정하기 위한 하나의 라이너로 재사용할 수 있습니다.
- 여전히 명령의 반환 코드에 액세스할 수 있습니다.
- 임시 파일 설명자 3을 희생합니다(물론 사용자가 변경할 수 있음).
- 그리고 이 임시 파일 설명자를 내부 명령에 노출하지 않습니다.
,▁catch▁want▁to▁고.stderr
command
안으로var
할수있습니다
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
나중에 모든 것을 얻을 수 있습니다.
echo "command gives $? and stderr '$var'";
한다면command
단순합니다(같은 것이 아닙니다).a | b
.{}
values:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
재사용할 수 있는 게재용가제포장로로 bash
은 -function에 3 합니다.)local -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
설명:
local -n
은 "$1"의 )catch-stderr
)3>&1
합니다.{ command; }
( 캡처("$@")$(..)
- 여기서는 정확한 순서가 중요합니다(잘못하면 파일 설명자가 잘못 섞입니다).
2>&1
stderr
$(..)
1>&3
stdout
캡처에서 력캡서벗어나출에처▁away▁from$(..)
처음부터 다시stdout
파일 기술자 3에 저장되었습니다.:stderr
여전히 FD 1이 앞에서 가리킨 위치를 나타냅니다.에 출력 로처캡로$(..)
3>&-
파일 이 더 하지 않기 에 닫힙니다. 됩니다.command
알 수 없는 열린 파일 설명자가 갑자기 나타나지 않습니다.열려 , FD 3은 FD 3입니다.command
볼 수 없을 것입니다.- 후자는 중요하다, 왜냐하면 몇몇 프로그램들은
lvm
예상치 못한 파일 설명자에 대한 불만.그리고.lvm
에게 불평을 늘어놓다.stderr
바로 우리가 포착할 것입니다!
이 레시피로 다른 파일 설명자를 잡을 수 있습니다.물론 파일 설명자 1을 제외하고(여기서는 리디렉션 로직이 잘못되지만, 파일 설명자 1의 경우에는 그냥 사용할 수 있습니다.var=$(command)
여느 때와 같이)
는 파일 30을 한다는 점에 하십시오.파일 설명자가 필요한 경우 번호를 자유롭게 변경할 수 있습니다.하지만 1980년대의 몇몇 조개들은 이해할 수도 있다는 것을 명심하세요.99>&1
9
에 뒤에9>&1
(은 (에 대해) 가 되지 bash
).
또한 변수를 통해 이 FD3를 구성하는 것은 특히 쉽지 않습니다.이로 인해 내용을 매우 읽을 수 없게 됩니다.
: catch-var-from-fd-by-fd variable fd-to-catch fd-to-sacrifice command [args..]
catch-var-from-fd-by-fd()
{
local -n v="$1";
local fd1="$2" fd2="$3";
shift 3 || return;
eval exec "$fd2>&1";
v="$(eval '"$@"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}
보안 참고:처음 3개의 인수는 다음과 같습니다.
catch-var-from-fd-by-fd
타사에서 가져가서는 안 됩니다.항상 "정적인" 방식으로 명시적으로 제공합니다.그래서 노노노노
catch-var-from-fd-by-fd $var $fda $fdb $command
절대 이러지 마!이름을 과 같이 .
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
이렇게 하면 모든 공격으로부터 사용자를 보호할 수는 없지만 적어도 일반적인 스크립트 오류를 탐지하고 방지하는 데 도움이 됩니다.
주의:
catch-var-from-fd-by-fd var 2 3 cmd..
는 와동합다니와 .catch-stderr var cmd..
shift || return
올바른 인수 개수를 제공하는 것을 잊어버린 경우 추한 오류를 방지하는 방법입니다.셸을 종료하는 것이 다른 방법일 수도 있습니다(그러나 이로 인해 명령줄에서 테스트하기가 어렵습니다).- 그 루틴은 이해하기 더 쉽도록 작성되었습니다.필요하지 않게 함수를 다시 작성할 수 있습니다.
exec
근데 진짜 못생기게 돼요. - 이 루틴은 다음을 위해 다시 작성될 수 있습니다.
bash
또한 그럴 필요가 없기 때문에local -n
하지만 그러면 로컬 변수를 사용할 수 없고 매우 못생기게 됩니다! - 한참고는 다음과 .
eval
s는 안전한 방식으로 사용됩니다. 통보.eval
위험한 것으로 간주됩니다.하지만 이 경우에는 사용하는 것보다 더 사악하지 않습니다."$@"
(임의 명령 실행).그러나 여기에 표시된 것처럼 정확하고 정확한 견적을 사용하십시오(그렇지 않으면 매우 위험해집니다).
# command receives its input from stdin.
# command sends its output to stdout.
exec 3>&1
stderr="$(command </dev/stdin 2>&1 1>&3)"
exitcode="${?}"
echo "STDERR: $stderr"
exit ${exitcode}
POSIX
STDERR은 몇 가지 리디렉션 마법을 사용하여 캡처할 수 있습니다.
$ { error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&3 ; } 2>&1); } 3>&1
lrwxrwxrwx 1 rZZt rZZt 7 Aug 22 15:44 /bin -> usr/bin/
$ echo $error
ls: cannot access '/XXXX': No such file or directory
서는 STDOUT(STDOUT하십시오.ls
안쪽에 있는 )의 에서 이루어집니다.{
}
파이프가 아닌 단순 명령을 실행하는 경우 이러한 내부 대괄호를 제거할 수 있습니다.
파이프가 하위 셸을 만들기 때문에 명령 외부로 파이프를 연결할 수 없습니다.bash
그리고.zsh
현재 셸에서는 하위 셸의 변수에 대한 할당을 사용할 수 없습니다.
때리기
bash
파일 설명자 3을 사용하지 않는다고 가정하지 않는 것이 좋습니다.
{ error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&$tmp ; } 2>&1); } {tmp}>&1;
exec {tmp}>&- # With this syntax the FD stays open
이 기능은 다음에서 작동하지 않습니다.zsh
.
일반적인 아이디어에 대한 이 답변 덕분입니다.
간단한 해결책
{ ERROR=$(./useless.sh 2>&1 1>&$out); } {out}>&1
echo "-"
echo $ERROR
생산 예정:
This Is Output
-
This Is Error
톰 헤일의 대답을 반복하면서, 저는 리다이렉션 요가를 더 쉽게 재사용할 수 있는 기능으로 포장하는 것이 가능하다는 것을 발견했습니다.예:
#!/bin/sh
capture () {
{ captured=$( { { "$@" ; } 1>&3 ; } 2>&1); } 3>&1
}
# Example usage; capturing dialog's output without resorting to temp files
# was what motivated me to search for this particular SO question
capture dialog --menu "Pick one!" 0 0 0 \
"FOO" "Foo" \
"BAR" "Bar" \
"BAZ" "Baz"
choice=$captured
clear; echo $choice
이것을 더 단순화하는 것은 거의 확실히 가능합니다.특별히 철저히 테스트하지는 않았지만 bash와 ksh 모두에서 작동하는 것으로 보입니다.
EDIT: 의 대체 버전capture
을 사용자 에 )$captured
), 레아 그리스의 대답에서 영감을 얻으면서 보존합니다.ksh
)zsh
은 다음과 같습니다.
capture () {
if [ "$#" -lt 2 ]; then
echo "Usage: capture varname command [arg ...]"
return 1
fi
typeset var captured; captured="$1"; shift
{ read $captured <<<$( { { "$@" ; } 1>&3 ; } 2>&1); } 3>&1
}
사용 방법:
capture choice dialog --menu "Pick one!" 0 0 0 \
"FOO" "Foo" \
"BAR" "Bar" \
"BAZ" "Baz"
clear; echo $choice
제가 한 방법은 다음과 같습니다.
#
# $1 - name of the (global) variable where the contents of stderr will be stored
# $2 - command to be executed
#
captureStderr()
{
local tmpFile=$(mktemp)
$2 2> $tmpFile
eval "$1=$(< $tmpFile)"
rm $tmpFile
}
사용 예:
captureStderr err "./useless.sh"
echo -$err-
임시 파일을 사용합니다.하지만 적어도 못생긴 것들은 기능에 싸여 있습니다.
이것은 제가 우아한 해결책이 있기를 바랐던 흥미로운 문제입니다.슬프게도, 저는 레플러 씨와 비슷한 해결책을 갖게 되었지만, 향상된 가독성을 위해 Bash 함수 내부에서 쓸모없는 것을 호출할 수 있다는 것을 추가하겠습니다.
#!/빈/빈 쓸모없는 기능/tmp/message.sh | sed 's/출력/쓸데없는/'} 오류 = $(쓸모없음)에코 $ERROR
다른 모든 종류의 출력 리디렉션은 임시 파일로 백업해야 합니다.
내 생각에 당신은 범인을 잡기를 원하는 것 같아요stderr
,stdout
그리고.exitcode
라면, 를 사용할 수 .
## Capture error when 'some_command() is executed
some_command_with_err() {
echo 'this is the stdout'
echo 'this is the stderr' >&2
exit 1
}
run_command() {
{
IFS=$'\n' read -r -d '' stderr;
IFS=$'\n' read -r -d '' stdout;
IFS=$'\n' read -r -d '' stdexit;
} < <((printf '\0%s\0%d\0' "$(some_command_with_err)" "${?}" 1>&2) 2>&1)
stdexit=${stdexit:-0};
}
echo 'Run command:'
if ! run_command; then
## Show the values
typeset -p stdout stderr stdexit
else
typeset -p stdout stderr stdexit
fi
는 이스트캡다니처합다를 합니다.stderr
,stdout
뿐만 아니라exitcode
.
하지만 어떻게 작동합니까?
먼저, 우리는 그것을 캡처합니다.stdout
뿐만 아니라exitcode
용사를 printf '\0%s\0%d\0'
▁the다로 됩니다.\0
일명 '수직 바이트'
그 후에, 우리는 리다이렉트를 합니다.printf
stderr
수행:1>&2
그런 다음 모든 것을 다시 연결합니다.stdout
용사를 2>&1
그므로러,,stdout
다음과 같이 표시됩니다.
"<stderr>\0<stdout>\0<exitcode>\0"
다을동니다합봉을 합니다.printf
.<( ... )
프로세스 대체를 수행합니다.프로세스 대체를 사용하면 파일 이름을 사용하여 프로세스의 입력 또는 출력을 참조할 수 있습니다.이것은 의미합니다.<( ... )
파이프를 피울 것입니다.stdout
(printf '\0%s\0%d\0' "$(some_command_with_err)" "${?}" 1>&2) 2>&1
stdin
첫 번째를 사용하는 명령 그룹의<
.
그러면, 우리는 그 파이프를 잡을 수 있습니다.stdout
stdin
에서 " " " 를 사용합니다.read
는 파일 " " " " 에서 한 .stdin
그리고 그것을 들판으로 나눕니다. 있문자만에 있는 $IFS
단어 구분 기호로 인식됩니다. $IFS
또는 내부 필드 구분 기호는 Bash가 문자열을 해석할 때 필드 또는 단어 경계를 인식하는 방법을 결정하는 변수입니다. $IFS
기본값은 공백(공백, 탭 및 새 줄)이지만 쉼표로 구분된 데이터 파일을 구문 분석하도록 변경할 수 있습니다.:$*
$에된 첫 번째 IFS에 저장된 첫 번째 문자를 사용합니다.
## Shows whitespace as a single space, ^I(horizontal tab), and newline, and display "$" at end-of-line.
echo "$IFS" | cat -vte
# Output:
# ^I$
# $
## Reads commands from string and assign any arguments to pos params
bash -c 'set w x y z; IFS=":-;"; echo "$*"'
# Output:
# w:x:y:z
for l in $(printf %b 'a b\nc'); do echo "$l"; done
# Output:
# a
# b
# c
IFS=$'\n'; for l in $(printf %b 'a b\nc'); do echo "$l"; done
# Output:
# a b
# c
그것이 우리가 정의한 이유입니다.IFS=$'\n'
(새 줄)을 구분 기호로 사용합니다.우리의 스크립트는 다음을 사용합니다.read -r -d ''
서, 디에어read -r
백슬래시는 문자를 이스케이프할 수 없습니다.-d ''
첫 번째 문자까지 계속됩니다.''
새 행이 아닌 읽혀집니다.
막으대체로를 대체합니다.some_command_with_err
을 캡처하고 처리할 수 있습니다.stderr
,stdout
뿐만 아니라exitcode
당신의 뜻대로
이 게시물은 제 자신의 목적을 위해 비슷한 솔루션을 고안하는 데 도움이 되었습니다.
MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`
그런 다음 MESSAGE가 빈 문자열이 아닌 한 다른 항목으로 전달합니다.이렇게 하면 format_logs.py가 python 예외의 일종으로 실패했는지 알 수 있습니다.
zsh 단위:
{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )
캡처 AND 인쇄 stderr
ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )
고장
사용할 수 있습니다.$()
캡처하려고 .그래서 당신은 stdout과 stderr를 바꿉니다.표준 스왑 알고리즘에서 fd3을 임시 저장소로 사용합니다.
하려면 캡처및인사원하경는우을용쇄▁use경우를 사용합니다.tee
사본을 작성합니다. 이경의출은의 tee
에 의해 캡처됩니다.$()
콘로가것는보다의솔, 만하지der stderr(의▁st콘솔r(,)).tee
이 출력을 두 합니다.tee
한 파일을 /dev/fd/2
이래tee
FD 번호가 아닌 파일 경로가 필요합니다.
참고: 그것은 한 줄로 된 엄청나게 많은 방향 변경이며 순서가 중요합니다. $()
의 약점을 잡는 것입니다.tee
경로를 지정합니다../useless.sh
의 tee
을 우리가 stdout으로 바꾼 후에../useless.sh
.
의 stdout을 사용합니다./쓸모가 없습니다.쉬
가 여전히 여전인 (히쇄만아니라을말싶는다했니습사다고용고하뿐▁op인)▁std▁like는▁the) stdout처럼 (만 하는 것이 아니라) ../useless.sh | sed 's/Output/Useless/'
.
문제 없습니다. stdout과 stderr을 스왑하기 전에 수행하십시오.기능 또는 파일로 이동하는 것이 좋습니다(또한 사용하지 않음).sh) ./dll을 대신하여 호출합니다.위 라인의 sh.
하지만, 만약 당신이 stdout과 stderr을 캡처하고 싶다면, 나는 당신이 임시 파일에 의존해야 한다고 생각합니다. 왜냐하면$()
에서는 한 번에 하나씩만 수행되며 변수를 반환할 수 없는 하위 셸을 만듭니다.
YellowApple의 답변 개선:
stderr을 임의의 변수로 캡처하는 Bash 함수입니다.
stderr_capture_example.sh
:
#!/usr/bin/env bash
# Capture stderr from a command to a variable while maintaining stdout
# @Args:
# $1: The variable name to store the stderr output
# $2: Vararg command and arguments
# @Return:
# The Command's Returnn-Code or 2 if missing arguments
function capture_stderr {
[ $# -lt 2 ] && return 2
local stderr="$1"
shift
{
printf -v "$stderr" '%s' "$({ "$@" 1>&3; } 2>&1)"
} 3>&1
}
# Testing with a call to erroring ls
LANG=C capture_stderr my_stderr ls "$0" ''
printf '\nmy_stderr contains:\n%s' "$my_stderr"
테스트:
bash stderr_capture_example.sh
출력:
stderr_capture_example.sh
my_stderr contains:
ls: cannot access '': No such file or directory
이 기능을 사용하여 반환된 선택 사항을 캡처할 수 있습니다.dialog
지휘권
임시 파일을 사용하지 않으려면 프로세스 대체를 사용할 수 있습니다.아직 제대로 작동하지 않았습니다.이것이 저의 첫 번째 시도였습니다.
$ .useless.sh 2> >( ERROR=$(<) )
-bash: command substitution: line 42: syntax error near unexpected token `)'
-bash: command substitution: line 42: `<)'
그리고 노력했습니다.
$ ./useless.sh 2> >( ERROR=$( cat <() ) )
This Is Output
$ echo $ERROR # $ERROR is empty
하지만
$ ./useless.sh 2> >( cat <() > asdf.txt )
This Is Output
$ cat asdf.txt
This Is Error
그래서 대체 과정은 일반적으로 옳은 일을 하고 있습니다.안타깝게도, STDIN을 안에 포장할 때마다.>( )
있는고지에 있는 $()
, 는 변캡을수려시는서도, 내잃립어니다버을의 내용을 .$()
이 제생에이 때문이라고 합니다.$()
상위 프로세스가 소유한 /dev/debug의 파일 설명자에 더 이상 액세스할 수 없는 하위 프로세스를 시작합니다.
프로세스 대체로 인해 더 이상 STDERR에 없는 데이터 스트림으로 작업할 수 있게 되었습니다. 안타깝게도 제가 원하는 방식으로 처리할 수 없는 것 같습니다.
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
명령을 오류 방지하는 경우:
execute [INVOKING-FUNCTION] [COMMAND]
execute () {
function="${1}"
command="${2}"
error=$(eval "${command}" 2>&1 >"/dev/null")
if [ ${?} -ne 0 ]; then
echo "${function}: ${error}"
exit 1
fi
}
린 제조에서 영감을 얻은 제품:
이용하겠습니다find
find / -maxdepth 2 -iname 'tmp' -type d
데모를 위한 비슈퍼 유저입니다.액세스할 때 '권한이 거부되었습니다'라고 불만을 표시해야 합니다./
dir.dir.dir.dir.
#!/bin/bash
echo "terminal:"
{ err="$(find / -maxdepth 2 -iname 'tmp' -type d 2>&1 1>&3 3>&- | tee /dev/stderr)"; } 3>&1 | tee /dev/fd/4 2>&1; out=$(cat /dev/fd/4)
echo "stdout:" && echo "$out"
echo "stderr:" && echo "$err"
출력을 제공합니다.
terminal:
find: ‘/root’: Permission denied
/tmp
/var/tmp
find: ‘/lost+found’: Permission denied
stdout:
/tmp
/var/tmp
stderr:
find: ‘/root’: Permission denied
find: ‘/lost+found’: Permission denied
그terminal
출력 또한 가지고 있습니다./dev/stderr
스크립트 없이 찾기 명령을 실행하는 것과 동일한 방법으로 내용을 표시합니다. $out
가지다/dev/stdout
그리고.$err
가지다/dev/stderr
내용물
사용:
#!/bin/bash
echo "terminal:"
{ err="$(find / -maxdepth 2 -iname 'tmp' -type d 2>&1 1>&3 3>&-)"; } 3>&1 | tee /dev/fd/4; out=$(cat /dev/fd/4)
echo "stdout:" && echo "$out"
echo "stderr:" && echo "$err"
보기 싫으면/dev/stderr
최종 출력에서.
terminal:
/tmp
/var/tmp
stdout:
/tmp
/var/tmp
stderr:
find: ‘/root’: Permission denied
find: ‘/lost+found’: Permission denied
언급URL : https://stackoverflow.com/questions/962255/how-to-store-standard-error-in-a-variable
'programing' 카테고리의 다른 글
엑셀 파일 작업을 위한 간단하고 신뢰할 수 있는 C 라이브러리는 무엇입니까? (0) | 2023.04.26 |
---|---|
Objective-C에서 객체가 어떤 클래스에 있는지 테스트하려면 어떻게 해야 합니까? (0) | 2023.04.26 |
문자열 형식을 사용하여 WPF XAML 바인딩에 문자열 추가 (0) | 2023.04.26 |
컨트롤러 기반 대 컨트롤러 for ASP에서 파생된 이유NET Core 웹 API? (0) | 2023.04.26 |
자르기 명령을 사용하여 공백을 구분 기호로 사용 (0) | 2023.04.26 |