같은 줄에 두 개의 난수를 출력하는 다음 bash 스크립트가 있습니다.
#!/bin/bash
for i in 1 2; do
unset var
until [ "$var" -lt 10000 ] 2>/dev/null; do
var="$RANDOM"
done
printf "%s," "${var/%,/}"
done
출력은 다음과 같습니다
5751,2129,
나는 $var = 5751,2129의 출력을 사용할 수 있도록 $var
시도한 끝에 있는 쉼표를 제거하려고 합니다 . "${var/%,/}"
누구든지 도와줄 수 있나요?
답변1
다음과 같이 할당하면 var="$RANDOM"
변수 var
는 확장의 문자열을 보유합니다 $RANDOM
. Bash에서는 범위 $RANDOM
에서 10진수로 확장됩니다 . 거기에는 캐릭터 가 없기 때문에 나중에 확장팩에서 제거하려고 할 필요가 없습니다 .0
32767
,
,
$var
출력에서 관찰된 쉼표 문자는 코드의 다음 부분에서 나왔습니다.
printf "%s," "${var/%,/}"
# here ^
이 명령은 루프의 각 반복에서 호출되었으므로 각 반복마다 출력에 하나의 쉼표 문자가 추가되었습니다.
인쇄된 내용은 인쇄 취소할 수 없습니다. 뉘앙스가 있습니다.
- 출력을 필터로 파이프하면 필터가 출력의 일부를 제거할 수 있습니다. 필터는 스크립트 외부에 있을 수 있습니다(예: 호출
the_script | the_filter
). 또는 일부 출력을 파이프할 수 있습니다.부분스크립트 내부의 필터에 스크립트를 추가합니다. 후자의 경우 전체 스크립트의 출력이 필터링됩니다. 여전히 스크립트의 일부에 의해 인쇄된 내용은 인쇄 취소되지 않습니다. 진심이야하다필터에 도착. 필터는 나중에 이를 제거합니다. - 터미널로 인쇄하는 경우 이전 출력의 일부를 새 데이터로 덮어쓰도록 할 수 있습니다. 커서를 이동할 수 있는 문자와 시퀀스가 있습니다. 아직도 그들은 모두 터미널에 도착합니다. 이전 출력을 거의 즉시 시각적으로 숨길 수 있지만 출력을 파일(또는 사용된 시퀀스를 이해하지 못하는 터미널)로 리디렉션하면 모든 내용이 거기에 있음을 알 수 있습니다.
원하지 않는 쉼표를 제거하는 올바른 방법은 처음부터 인쇄하지 않는 것입니다. 또는 스크립트 내에서 필터링합니다. 이를 수행하는 방법은 여러 가지가 있으며 모든 방법을 찾는 것은 내 의도가 아닙니다. 나는 그들 중 일부를 논의할 것이다. 두 번 이상의 반복이 있는 루프에 대해서도 가능한 접근 방식을 알고 싶다고 가정합니다. 반복 횟수를 미리 알 수 없는 for 루프일 수도 있습니다. 어쩌면 숫자로 열거되지 않은 for 루프일 수도 있습니다. 아마도 끝나지 않을 수도 있는 for 루프일 수도 있습니다(예: while true
대신 for
).
참고: 를 사용하면 printf "%s," "${var/%,/}"
후행 개행 문자가 인쇄되지 않습니다. 가능하다면 이 동작을 재현하도록 노력하겠습니다.
가능한 접근 방식은 거의 없습니다.
루프 내부는 에 의존하지 않습니다
$i
. 루프를 제거하고 두 개의 별도 변수를 사용할 수 있습니다.unset var1 until [ "$var1" -lt 10000 ] 2>/dev/null; do var1="$RANDOM" done unset var2 until [ "$var2" -lt 10000 ] 2>/dev/null; do var2="$RANDOM" done printf '%s,%s' "$var1" "$var2"
노트:
- 그렇지 않다마른.
- 확장이 잘 되지 않습니다. (만약 당신이 있었다면
for i in {1..100}
?) - 반복 횟수를 미리 알 수 없으면 번거롭습니다.
현재 코드를 파이프에 넣고 후행 쉼표를 필터링할 수 있습니다. 예:
for i in 1 2; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done printf "%s," "$var" done | sed 's/,$//'
노트:
sed
(또는 사용하는 필터가 무엇이든) 루프가 생성하는 불완전한 줄(개행 문자로 끝나지 않는 줄)을 처리할 수도 있고 처리하지 않을 수도 있습니다. 이 경우sed
구현에 따라 다릅니다.sed
처리하는 경우에도 여전히 개행 문자로 출력을 종료할 수 있습니다.sed
(또는 사용하는 필터가 무엇이든) 너무 긴 줄을 처리하지 못할 수 있습니다. 위의 특정 코드는 합리적으로 짧은 줄을 생성하지만 일반적으로(많은 반복을 상상해 보십시오) 길이가 문제가 될 수 있습니다.sed
는 라인 지향 도구로서 처리하기 전에 전체 라인을 읽어야 합니다. 이 경우 전체 입력을 읽어야 합니다. 모든 반복이 끝날 때까지 아무 것도 얻지 못할 것입니다.- 루프는 서브쉘에서 실행됩니다. 일반적으로 어떤 이유로든 메인 쉘에서 작동하기를 원할 수 있습니다.
현재 코드의 출력을 변수로 캡처할 수 있습니다. 마지막에는 변수를 확장하는 동안 후행 쉼표를 제거합니다.
capture="$(for i in 1 2; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done printf "%s," "$var" done)" printf "%s" "${capture%,}"
노트:
- 루프는 서브쉘에서 실행됩니다. 일반적으로 어떤 이유로든 메인 쉘에서 작동하기를 원할 수 있습니다.
- 코드는 마지막
printf
(루프 외부)에 도달할 때까지 침묵합니다. 모든 반복이 끝날 때까지 아무 것도 얻지 못할 것입니다. - 일반적으로 루프는 원하는 수의 바이트를 출력할 수 있습니다. 나는 Bash가 상당한 양의 데이터를 변수에 저장할 수 있다고 생각합니다.
printf
은 내장되어 있기 때문에 가능합니다.아마printf "%s" "${capture%,}"
때리지 않고 다루다명령줄 길이 제한. 어쨌든 쉘 변수에 많은 양의 데이터를 저장하는 IMO는 최선의 방법이 아니기 때문에 이것을 철저하게 테스트하지 않았습니다. 출력의 길이가 제한되어 있다는 것을 안다면 여전히 연습이 정당화될 수 있습니다. (참고로 위 코드는 확실히 매우 짧은 출력을 생성합니다.) - Bash는 NUL 문자를 변수에 저장할 수 없습니다(대부분의 쉘은 할 수 없습니다. zsh는 할 수 있습니다). 또한
$()
후행 줄 바꿈을 모두 제거합니다. 이는 변수를 사용하여 데이터를 저장할 수 없음을 의미합니다.임의의출력하고 나중에 정확하게 재현합니다. (참고로 위 코드에서 내부 조각은$()
NUL이나 후행 줄 바꿈을 생성하지 않습니다.)
출력을 캡처하는 대신 각 반복이 일부 변수에 추가되도록 할 수 있습니다.
capture='' for i in 1 2; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done capture="$capture$var," done printf "%s" "${capture%,}"
노트:
- 코드는 메인 셸(서브 셸이 아님)에서 실행됩니다.
- 변수에 데이터를 저장할 때의 제한 사항(이전 방법 참조)은 여전히 적용됩니다.
- Bash에서는
capture+="$var,"
. (참고: 변수에 대해 정수 속성이 설정된 경우=+
"추가"가 아니라 "추가"를 의미합니다..)
마지막 반복을 감지하고 다음 없이 형식을 사용할 수 있습니다
,
.# this example is more educative with more than two iterations for i in {1..5}; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done if [ "$i" -eq 5 ]; then printf "%s" "$var" else printf "%s," "$var" fi done
노트:
- 서브쉘이 없습니다.
- 숫자를 미리 모르면 마지막 반복을 감지하는 것이 더 어렵습니다.
- 배열(예: )을 반복하는 경우 더욱 어렵습니다
for i in "${arr[@]}"
. - 각 반복은 즉시 인쇄되며 순차적으로 출력됩니다. 루프가 무한하더라도 이는 작동합니다.
첫 번째 반복을 감지하고
,
. 원래 코드,%s
대신 사용할 수도 있습니다 .%s,
그러면 당신은,5751,2129
대신에 얻을 것입니다5751,2129,
. 이 변경을 통해 후행 쉼표를 피하거나 제거하는 위의 방법 중 하나를 선행 쉼표를 피하거나 제거하는 방법으로 변환할 수 있습니다. 마지막 방법은 다음과 같습니다.# this example is more educative with more than two iterations for i in {1..5}; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done if [ "$i" -eq 1 ]; then printf "%s" "$var" else printf ",%s" "$var" fi done
노트:
- 서브쉘이 없습니다.
1
항상 시작하는 경우 (또는 일반적으로 고정된 고유 문자열) 첫 번째 반복을 감지하는 것은 쉽습니다 .- 하지만 배열을 반복하면 더 어렵습니다
for i in "${arr[@]}"
. 나중에 배열if [ "$i" = "${arr[1]}" ]
에 동일한 요소가 있을 수 있으므로 확인하면 안 됩니다 ."${arr[1]}"
이를 처리하는 간단한 방법은 루프에 대한 인덱스를 유지하고(index=1
루프 이전에 매 반복이 끝날 때마다 1씩 증가) 그 값을1
; 하지만 그런 코드는 다소 번거롭다고 생각합니다. - 각 반복은 즉시 인쇄되며 순차적으로 출력됩니다. 루프가 무한하더라도 이는 작동합니다.
,
자신을 변수에서 가져오도록 만들 수 있습니다 . 변수가 비어 있는 루프를 입력하고,
각 반복이 끝날 때마다 변수를 설정합니다. 이렇게 하면 작업이 끝날 때 값이 한 번만 효과적으로 변경됩니다.첫 번째반복. 예:# this example is more educative with more than two iterations separator='' for i in {1..5}; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done printf "%s%s" "$separator" "$var" separator=, done
노트:
- 서브쉘이 없습니다.
- 배열을 반복하는 경우에도 잘 작동합니다.
- 각 반복은 즉시 인쇄되며 순차적으로 출력됩니다. 루프가 무한하더라도 이는 작동합니다.
개인적으로 나는 이 방법이 매우 우아하다고 생각합니다.
일반 사항:
- 각 스니펫은 후행 개행 없이 출력을 생성합니다(필터가 있거나 다른 필터가 있는 경우는 제외
sed
). 적절하게 종료된 텍스트 줄을 형성하기 위해 전체 출력이 필요한 경우 루프 다음에 실행printf '\n'
하거나 단독으로 실행하십시오.echo
- 당신은
,
종결자가 아니라 분리자가 되기를 원합니다. 즉, 반복이 정확히 0개인 루프는$var
반복이 빈 문자열로 확장되는 경우 정확히 1개의 반복이 있는 루프와 동일한 빈 출력을 생성합니다. 우리의 경우에는$var
매번 비어 있지 않은 문자열로 확장되며 반복 횟수가 0회 이상이라는 것을 알고 있습니다. 그러나 일반적인 경우 종결자 대신 구분 기호를 사용하면 모호해질 수 있습니다.