파이프, 이동 또는 매개변수 확장 중 어느 것이 더 효율적입니까?

파이프, 이동 또는 매개변수 확장 중 어느 것이 더 효율적입니까?

공백으로 구분된 단어 목록에서 서로 일정한 수의 값이 떨어져 있는 일부 값을 반복하는 가장 효율적인 방법을 찾으려고 합니다(배열을 사용하고 싶지 않습니다). 예를 들어,

list="1 ant bat 5 cat dingo 6 emu fish 9 gecko hare 15 i j"

그래서 목록을 반복하고 1, 5, 6, 9, 15에만 액세스할 수 있기를 원합니다.

편집하다:목록에서 얻으려는 값이 목록의 나머지 부분과 다른 형식일 필요는 없다는 점을 분명히 해야 합니다. 이는 목록에서의 위치(이 경우 위치 1, 4, 7...)에서만 특별합니다. 따라서 목록이 될 수 있지만 1 2 3 5 9 8 6 90 84 9 3 2 15 75 55여전히 동일한 숫자를 원합니다. 그리고 나는 목록의 길이를 모른다고 가정하고 이 작업을 수행할 수 있기를 원합니다.

현재까지 제가 생각할 수 있는 방법은 다음과 같습니다.

방법 1

set $list
found=false
find=9
count=1
while [ $count -lt $# ]; do
    if [ "${@:count:1}" -eq $find ]; then
    found=true
    break
    fi
    count=`expr $count + 3`
done

방법 2

set list
found=false
find=9
while [ $# ne 0 ]; do
    if [ $1 -eq $find ]; then
    found=true
    break
    fi
    shift 3
done

방법 3 나는 배관이 이것을 최악의 선택으로 만든다고 확신하지만 호기심 때문에 세트를 사용하지 않고 이를 수행할 수 있는 방법을 찾으려고 노력하고 있습니다.

found=false
find=9
count=1
num=`echo $list | cut -d ' ' -f$count`
while [ -n "$num" ]; do
    if [ $num -eq $find ]; then
    found=true
    break
    fi
    count=`expr $count + 3`
    num=`echo $list | cut -d ' ' -f$count`
done

그렇다면 가장 효율적인 방법은 무엇입니까? 아니면 더 쉬운 방법을 놓치고 있습니까?

답변1

  • 소프트웨어 최적화의 첫 번째 규칙:아니요.

    문제가 있다는 것을 알기 전까지는 프로그램의 속도에 대해 생각할 필요가 없습니다. 목록의 길이가 이 정도이거나 항목이 100~1000개 정도인 경우에는 시간이 얼마나 걸리는지조차 눈치채지 못할 것입니다. 차이점보다 최적화에 대해 생각하는 데 더 많은 시간을 할애할 수 있습니다.

  • 두 번째 규칙:측정하다.

    이는 알아내는 확실한 방법이자 시스템에 대한 답을 제공하는 방법입니다. 특히 조개껍데기는 종류가 다양하며 모두 똑같지는 않습니다. 하나의 셸에 특정한 답변이 귀하의 셸에 적용되지 않을 수도 있습니다.

    대규모 프로그램에서는 여기에서 분석도 수행됩니다. 가장 느린 부분은 당신이 상상하는 것과 다를 수도 있습니다.

  • 3. 쉘 스크립트 최적화의 첫 번째 규칙:쉘을 사용하지 마십시오.

    네 진짜로 요. 많은 쉘은 빠르게 설계되지 않았으며(외부 프로그램을 시작할 필요가 없기 때문에) 매번 소스 라인을 다시 구문 분석할 수도 있습니다.

    대신 awk나 Perl과 같은 것을 사용하세요. 제가 수행한 간단한 마이크로벤치마크에서는 awkI/O 없이 간단한 루프를 실행하는 것이 일반적인 셸보다 수십 배 더 빨랐습니다.

    그러나 쉘을 사용하는 경우 외부 명령보다는 쉘의 내장 기능을 사용하십시오. 여기서 사용하는 것은 expr내 시스템에서 찾은 어떤 쉘에도 내장되어 있지 않지만 표준 산술 확장으로 대체될 수 있습니다. 예를 들어 i=$((i+1)), . 이전 예에서 사용한 것을 표준 매개변수 확장으로 대체할 수도 있습니다.i=$(expr $i + 1)icut

    또한보십시오:쉘 루프를 사용하여 텍스트를 처리하는 것이 왜 나쁜 습관으로 간주됩니까?

문제에 1단계와 2단계를 적용해야 합니다.

답변2

매우 간단합니다 awk. 그러면 모든 길이 입력의 4개 필드마다 값이 제공됩니다.

$ awk -F' ' '{for( i=1;i<=NF;i+=3) { printf( "%s%s", $i, OFS ) }; printf( "\n" ) }' <<< $list
1 5 6 9 15

이는 (레코드의 필드 수) awk와 같은 기본 제공 변수를 활용 하고 몇 가지 간단한 루프를 수행하여 필드를 반복하여 필드 수를 미리 알 필요 없이 원하는 필드를 제공할 수 있습니다.NFfor

또는 예제에 지정된 특정 필드만 원하는 경우:

$ awk -F' ' '{ print $1, $4, $7, $10, $13 }' <<< $list
1 5 6 9 15

효율성에 대한 질문의 경우 가장 쉬운 방법은 이 방법이나 다른 모든 방법을 테스트하고 이를 사용하여 View System Call Flow와 같은 도구를 time사용할 수도 있습니다 . strace사용법은 time다음과 같습니다:

$ time ./script.sh

real    0m0.025s
user    0m0.004s
sys     0m0.008s

다양한 방법 간의 결과를 비교하여 시간 측면에서 가장 효율적인 방법을 확인할 수 있습니다. 다른 효율성 지표에는 다른 도구를 사용할 수 있습니다.

답변3

이 답변에서는 벤치마크가 아닌 일반적인 조언만 제공하겠습니다. 벤치마크는 성능에 대한 질문에 확실하게 대답할 수 있는 유일한 방법입니다. 그런데 네가 말을 안 하니까.얼마나귀하가 작업 중인 데이터와얼마나 자주이렇게 하면 유용한 벤치마킹을 할 수 없습니다. 일반적으로 10개의 프로젝트로 더 효율적이 되는 것과 1,000,000개의 프로젝트로 더 효율적으로 되는 것 사이에는 차이가 있습니다.

일반적으로 순수 쉘 코드에 루프가 포함되지 않는 한 외부 명령을 호출하는 것은 순수 쉘 구성을 사용하여 작업을 수행하는 것보다 비용이 더 많이 듭니다. 반면, 큰 문자열이나 많은 수의 문자열을 반복하는 쉘 루프는 특수 도구를 호출하는 것보다 느릴 수 있습니다. 예를 들어, 루프 호출은 cut실제로 상당히 느릴 수 있지만 한 번의 호출로 전체 작업을 수행하는 방법을 찾으면 cut셸에서 문자열 조작을 사용하여 동일한 작업을 수행하는 것보다 더 빠를 수 있습니다.

컷오프 지점은 시스템마다 크게 다를 수 있습니다. 이는 커널, 커널 스케줄러 구성 방법, 외부 실행 파일이 포함된 파일 시스템, 현재 CPU 및 메모리 압력 및 기타 여러 요인에 따라 달라질 수 있습니다.

expr성능에 전혀 관심이 있다면 산술을 수행하기 위해 전화하지 마십시오. 실제로 expr산술을 수행하기 위해 호출이 전혀 이루어지지 않습니다. 쉘에는 expr.

sh에 존재하지 않는 bash 구성을 사용하고 있기 때문에 bash를 사용하는 것 같습니다. 그렇다면 어쨌든 배열을 사용하지 않는 이유는 무엇입니까? 배열은 가장 자연스러운 솔루션이며 아마도 가장 빠른 솔루션일 것입니다. 배열 인덱싱은 0부터 시작됩니다.

list=(1 2 3 5 9 8 6 90 84 9 3 2 15 75 55)
for ((count = 0; count += 3; count < ${#list[@]})); do
  echo "${list[$count]}"
done

shsh를 사용하는 경우 시스템이 bash 대신 dash 또는 ksh를 사용하면 스크립트가 더 빨라질 수 있습니다. sh를 사용하면 명명된 배열을 얻지 못하지만 를 사용하여 설정할 수 있는 위치 인수 배열 중 하나를 얻을 수 있습니다 set. 런타임까지 알 수 없는 위치의 요소에 액세스하려면 를 사용해야 합니다 eval(올바른 인용에 주의하세요!).

# List elements must not contain whitespace or ?*\[
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
set $list
count=1
while [ $count -le $# ]; do
  eval "value=\${$count}"
  echo "$value"
  count=$((count+1))
done

배열에 한 번만 왼쪽에서 오른쪽으로(특정 값을 건너뛰고) 액세스하려는 경우 shift대신 변수 인덱싱을 사용할 수 있습니다.

# List elements must not contain whitespace or ?*\[
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
set $list
while [ $# -ge 1 ]; do
  echo "$1"
  shift && shift && shift
done

어떤 방법이 더 빠른지는 쉘과 요소 수에 따라 다릅니다.

또 다른 가능성은 문자열 처리를 사용하는 것입니다. 위치 매개변수를 사용하지 않는다는 장점이 있어 다른 용도로 사용할 수 있습니다. 데이터 양이 많으면 속도가 느려지지만, 양이 적으면 눈에 띄는 차이가 발생하지 않습니다.

# List elements must be separated by a single space (not arbitrary whitespace)
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
while [ -n "$list" ]; do
  echo "${list% *}"
  case "$list" in *\ *\ *\ *) :;; *) break;; esac
  list="${list#* * * }"
done

답변4

아마 이거?

cut -d' ' -f1,4,7,10,13 <<<$list
1 5 6 9 15

관련 정보