Bash에서 문자열의 각 문자에 대해 for 루프를 수행하는 방법은 무엇입니까?
다음과 같은 변수가 있습니다.
words="这是一条狗。"
나는 각 문자를 한 번에 하나씩, 예를 들어 처음에 루프를 만들고 싶어 character="这", 다음 character="是", character="一"등
내가 아는 유일한 방법은 파일의 개별 줄에 각 문자를 출력 한 다음를 사용 while read line하는 것입니다. 그러나 이것은 매우 비효율적으로 보입니다.
- for 루프를 통해 문자열의 각 문자를 어떻게 처리 할 수 있습니까?
로 sed에서 dash의 쉘 LANG=en_US.UTF-8, 나는 다음이 제대로 작동 가지고 :
$ echo "你好嗎 新年好。全型句號" | sed -e 's/\(.\)/\1\n/g'
你
好
嗎
新
年
好
。
全
型
句
號
과
$ echo "Hello world" | sed -e 's/\(.\)/\1\n/g'
H
e
l
l
o
w
o
r
l
d
따라서 출력은 while read ... ; do ... ; done
샘플 텍스트 편집을 영어로 번역 :
"你好嗎 新年好。全型句號" is zh_TW.UTF-8 encoding for:
"你好嗎" = How are you[ doing]
" " = a normal space character
"新年好" = Happy new year
"。全型空格" = a double-byte-sized full-stop followed by text description
C 스타일 for루프를 사용할 수 있습니다 .
foo=string
for (( i=0; i<${#foo}; i++ )); do
echo "${foo:$i:1}"
done
${#foo}길이로 확장됩니다 foo. 길이 1 ${foo:$i:1}에서 시작하는 하위 문자열로 확장됩니다 $i.
${#var} 길이를 반환 var
${var:pos:N}pos이후부터 N 개의 문자를 반환합니다.
예 :
$ words="abc"
$ echo ${words:0:1}
a
$ echo ${words:1:1}
b
$ echo ${words:2:1}
c
그래서 반복하기 쉽습니다.
또 다른 방법:
$ grep -o . <<< "abc"
a
b
c
또는
$ grep -o . <<< "abc" | while read letter; do echo "my letter is $letter" ; done
my letter is a
my letter is b
my letter is c
나는 아무도 분명한 언급하지 않았다 놀랐어요 bash만을 사용하는 솔루션을 while하고 read.
while read -n1 character; do
echo "$character"
done < <(echo -n "$words")
echo -n마지막에 불필요한 줄 바꿈을 피하기 위해 의 사용에 유의하십시오 . printf또 다른 좋은 옵션이며 특정 요구에 더 적합 할 수 있습니다. 당신이 공백 무시하려는 경우 교체 "$words"와 함께 "${words// /}".
또 다른 옵션은 fold. 그러나 for 루프에 입력해서는 안됩니다. 대신 다음과 같이 while 루프를 사용하십시오.
while read char; do
echo "$char"
done < <(fold -w1 <<<"$words")
fold( coreutils 패키지의) 외부 명령 사용의 주요 이점 은 간결함입니다. 다음과 같이 xargs( findutils 패키지의 일부) 와 같은 다른 명령에 출력을 제공 할 수 있습니다 .
fold -w1 <<<"$words" | xargs -I% -- echo %
echo위의 예에서 사용 된 명령을 각 문자에 대해 실행 하려는 명령 으로 바꾸고 싶을 것입니다. 참고 xargs기본적으로 공백 무시합니다. -d '\n'해당 동작을 비활성화하는 데 사용할 수 있습니다 .
국제화
방금 fold일부 아시아 문자로 테스트 한 결과 유니 코드가 지원되지 않는다는 것을 깨달았습니다. 따라서 ASCII 요구 사항에는 문제가 없지만 모든 사람에게 적용되는 것은 아닙니다. 이 경우 몇 가지 대안이 있습니다.
아마도 fold -w1awk 배열로 대체 할 것입니다 .
awk 'BEGIN{FS=""} {for (i=1;i<=NF;i++) print $i}'
또는 grep다른 답변에 언급 된 명령 :
grep -o .
공연
참고로 앞서 언급 한 3 가지 옵션을 벤치마킹했습니다. 처음 두 개는 빠르며 거의 묶였으며 폴드 루프는 while 루프보다 약간 빠릅니다. 당연히 xargs가장 느 렸습니다. 75 배 더 느 렸습니다.
다음은 (축약 된) 테스트 코드입니다.
words=$(python -c 'from string import ascii_letters as l; print(l * 100)')
testrunner(){
for test in test_while_loop test_fold_loop test_fold_xargs test_awk_loop test_grep_loop; do
echo "$test"
(time for (( i=1; i<$((${1:-100} + 1)); i++ )); do "$test"; done >/dev/null) 2>&1 | sed '/^$/d'
echo
done
}
testrunner 100
결과는 다음과 같습니다.
test_while_loop
real 0m5.821s
user 0m5.322s
sys 0m0.526s
test_fold_loop
real 0m6.051s
user 0m5.260s
sys 0m0.822s
test_fold_xargs
real 7m13.444s
user 0m24.531s
sys 6m44.704s
test_awk_loop
real 0m6.507s
user 0m5.858s
sys 0m0.788s
test_grep_loop
real 0m6.179s
user 0m5.409s
sys 0m0.921s
모든 공백 문자를 올바르게 보존하고 충분히 빠른 이상적인 솔루션은 아직 없다고 생각하므로 답변을 게시하겠습니다. ${foo:$i:1}작업을 사용 하지만 매우 느립니다. 특히 아래에 보여 주듯이 큰 문자열에서 두드러집니다.
내 아이디어는 Six가 제안한 방법의 확장으로 read -n1모든 문자를 유지하고 모든 문자열에 대해 올바르게 작동하도록 일부 변경 사항을 포함 합니다.
while IFS='' read -r -d '' -n 1 char; do
# do something with $char
done < <(printf %s "$string")
작동 원리 :
IFS=''-내부 필드 구분자를 빈 문자열로 재정의하면 공백과 탭이 제거되지 않습니다. 동일한 행에서 수행하면read다른 쉘 명령에 영향을주지 않습니다.-r- 줄 끝을 특수 줄 연결 문자로read처리 하지 못하게하는 "원시"를 의미\합니다.-d ''-빈 문자열을 구분 기호로 전달하면read개행 문자가 제거되지 않습니다. 실제로는 널 바이트가 구분 기호로 사용됨을 의미합니다.-d ''과 같습니다-d $'\0'.-n 1-한 번에 한 문자 씩 읽는다는 의미입니다.printf %s "$string"- 사용printf대신하기echo -n때문에, 안전echo취급-n및-e옵션으로. "-e"를 문자열로 전달하면echo아무것도 인쇄하지 않습니다.< <(...)- Passing string to the loop using process substitution. If you use here-strings instead (done <<< "$string"), an extra newline character is appended at the end. Also, passing string through a pipe (printf %s "$string" | while ...) would make the loop run in a subshell, which means all variable operations are local within the loop.
Now, let's test the performance with a huge string. I used the following file as a source:
https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt
The following script was called through time command:
#!/bin/bash
# Saving contents of the file into a variable named `string'.
# This is for test purposes only. In real code, you should use
# `done < "filename"' construct if you wish to read from a file.
# Using `string="$(cat makefiles.txt)"' would strip trailing newlines.
IFS='' read -r -d '' string < makefiles.txt
while IFS='' read -r -d '' -n 1 char; do
# remake the string by adding one character at a time
new_string+="$char"
done < <(printf %s "$string")
# confirm that new string is identical to the original
diff -u makefiles.txt <(printf %s "$new_string")
And the result is:
$ time ./test.sh
real 0m1.161s
user 0m1.036s
sys 0m0.116s
As we can see, it is quite fast.
Next, I replaced the loop with one that uses parameter expansion:
for (( i=0 ; i<${#string}; i++ )); do
new_string+="${string:$i:1}"
done
The output shows exactly how bad the performance loss is:
$ time ./test.sh
real 2m38.540s
user 2m34.916s
sys 0m3.576s
The exact numbers may very on different systems, but the overall picture should be similar.
I've only tested this with ascii strings, but you could do something like:
while test -n "$words"; do
c=${words:0:1} # Get the first character
echo character is "'$c'"
words=${words:1} # trim the first character
done
It is also possible to split the string into a character array using fold and then iterate over this array:
for char in `echo "这是一条狗。" | fold -w1`; do
echo $char
done
The C style loop in @chepner's answer is in the shell function update_terminal_cwd, and the grep -o . solution is clever, but I was surprised not to see a solution using seq. Here's mine:
read word
for i in $(seq 1 ${#word}); do
echo "${word:i-1:1}"
done
Another approach, if you don't care about whitespace being ignored:
for char in $(sed -E s/'(.)'/'\1 '/g <<<"$your_string"); do
# Handle $char here
done
Another way is:
Characters="TESTING"
index=1
while [ $index -le ${#Characters} ]
do
echo ${Characters} | cut -c${index}-${index}
index=$(expr $index + 1)
done
I share my solution:
read word
for char in $(grep -o . <<<"$word") ; do
echo $char
done
TEXT="hello world"
for i in {1..${#TEXT}}; do
echo ${TEXT[i]}
done
where {1..N} is an inclusive range
${#TEXT} is a number of letters in a string
${TEXT[i]} - you can get char from string like an item from an array
'program story' 카테고리의 다른 글
| Laravel 5+ Ubuntu 14.04에서 500 내부 서버 오류 발생 (0) | 2020.11.22 |
|---|---|
| UITextField 최대 길이 설정 (0) | 2020.11.22 |
| Mongo에서 $ addToSet에 대한 키를 지정할 수 있습니까? (0) | 2020.11.21 |
| Markdown에서 오른쪽 정렬 및 양쪽 정렬 방법은 무엇입니까? (0) | 2020.11.21 |
| 디버그 메시지 "리소스가 다른 것으로 해석되었지만 MIME 유형 application / javascript로 전송 됨" (0) | 2020.11.21 |