특정 쉘 스크립트의 `sed` 사용법에 대한 설명

특정 쉘 스크립트의 `sed` 사용법에 대한 설명

읽는 동안온라인 튜토리얼, 다음 코드를 발견했습니다.

#!/bin/bash
# Counting the number of lines in a list of files
# for loop over arguments
# count only those files I am owner of

if [ $# -lt 1 ]
then
  echo "Usage: $0 file ..."
  exit 1
fi

echo "$0 counts the lines of code" 
l=0
n=0
s=0
for f in $*
do
  if [ -O $f ] # checks whether file owner is running the script
  then 
      l=`wc -l $f | sed 's/^\([0-9]*\).*$/\1/'`
      echo "$f: $l"
      n=$[ $n + 1 ]
      s=$[ $s + $l ]
  else
      continue
  fi
done

echo "$n files in total, with $s lines in total"

sed이 예에서 호출의 목적은 무엇입니까?

답변1

예제 6의 명령은 sed출력에서 ​​행 수만 추출합니다 wc -l.

실행 중입니다 wc -l( $f인수로 전달된 스크립트가 소유한 파일). 일반적으로 다음과 같은 출력이 생성됩니다.

$ wc -l .bashrc
17 .bashrc

1열의 행 수와 2열의 파일 이름입니다. 이 sed명령은 매우 불필요한 방식으로 행 수만 가져옵니다.

$ wc -l .bashrc | sed 's/^\([0-9]*\).*$/\1/'
17

sed문은 's/^\([0-9]*\).*$/\1/'다음을 수행합니다.

  • ^- 줄의 시작과 일치합니다.
  • \([0-9]*\)- 숫자를 무제한으로 일치시킵니다(대괄호를 이스케이프 처리하면 캡처 그룹이 형성됨).
  • .*- 무엇이든 무제한으로 일치
  • $- 줄 끝 일치
  • \1- 첫 번째 캡처 그룹의 내용을 나타냅니다.

기본적으로 이는 숫자로 시작하는 모든 줄과 일치하고 전체 줄을 첫 번째 캡처 그룹(번호)으로 바꿉니다.


이것을 추천해준 Stephen Kitt에게 감사드립니다:

$ wc -l < .bashrc
17

그렇지 않으면 아래와 같이 cut사용 하는 것이 더 좋습니다 .awk

$ wc -l .bashrc | cut -d' ' -f1
17

$ wc -l .bashrc | awk '{print $1}'
17

답변2

이 코드의 목적 sed은 출력을 구문 분석하여 wc -l파일의 줄 수를 추출하는 것입니다.

이는 일반적으로 필요하지 않습니다.

l=$( wc -l <"$f" )

같은 일을 할 것입니다(당신은 이것을 시도해야 합니다).


스크립트는 이식 가능하지 않고 "오래된" 것으로 간주되는 일부 구성을 사용하며 스크립트에는 안전하지 않게 만드는 몇 가지 세부 사항이 있습니다.

  1. 확장명을 인용해야 합니다. 예를 들어 if [ $# -lt 1 ]을 쓰는 것이 더 좋고 if [ "$#" -eq 0 ], 을 if [ -O $f ]써야 합니다 if [ -O "$f" ]. 이런 방식으로 우리는 모든 문자, 심지어 그 안에 있는 문자 $IFS(공백, 탭 및 줄 바꿈) 를 포함하는 파일 이름을 지원할 수 있습니다 . 어떤 이유로든 숫자를 포함하는 $#경우에는 $IFS따옴표로 묶어야 합니다.

    이 콘텐츠에 대한 자세한 내용은 다음 세 가지 질문을 참조하세요.bash/POSIX 쉘에서 변수를 인용하는 것을 잊어버리는 보안 위험","공백이나 기타 특수 문자 때문에 쉘 스크립트가 멈추는 이유는 무엇입니까?" 그리고"언제 큰따옴표가 필요합니까?".

  2. 어떤 경우에는 명령 대체에 백틱을 사용하는 것이 번거롭습니다. 해당 줄은 l=`wc -l ...`다음과 같이 다시 작성할 수 있습니다 l=$(wc -l ...). 최신 줄은 중첩되어 있고 참조가 예상대로 작동하며(예를 들어 구문 오류를 생성하는 것과 비교하면 ) 읽기가 더 쉽기 때문에 $(...)더 좋습니다 .echo "`echo "`echo ok`"`"echo "$(echo "$(echo ok)")"

    이에 대한 자세한 내용은 "*sh 쉘에서는 백틱(예: "cmd")이 더 이상 사용되지 않습니까?"

  3. $[ $s + $l ]이식할 수 없는 명령문입니다 $(( s + l )).

  4. printf변수 데이터는 를 사용하는 대신 을 사용하여 출력해야 합니다 echo. 예를 들어 마지막 줄은

    echo "$n files in total, with $s lines in total"
    

    다음과 같이 다시 작성할 수 있습니다.

    printf '%d files in total, with %d lines in total\n' "$n" "$s"
    

    예를 들어 "왜 printf가 echo보다 나은가요?".

  5. 루프를 사용하여 $*명령줄 인수를 반복하면 공백이 포함된 파일 이름에서 스크립트가 실행되지 않습니다.

  6. 해당 continue명령문과 else해당 명령문의 분기는 어쨌든 루프의 끝에 있기 때문에 전혀 필요하지 않습니다.if

  7. 진단 출력은 표준 오류로 인쇄되어야 합니다.

스크립트의 "수정된" 버전:

#!/bin/bash
# Counting the number of lines in a list of files
# for loop over arguments
# count only those files I am owner of

if [ "$#" -eq 0 ]; then
    printf 'Usage: %s file ...\n' "$0" >&2
    exit 1
fi

printf '%s counts the lines of code\n' "$0"
l=0; n=0; s=0
for name do
    if [ -f "$name" ] && [ -O "$name" ]; then # checks whether its a regular file and file owner is running the script
        nlines=$( wc -l <"$name" )
        printf '%s: %d\n' "$name" "$nlines"
        totlines=$(( totlines + nlines ))
        nfiles=$(( nfiles + 1 ))
    fi
done

printf '%d files in total, with %s lines in total" "$nfiles" "$totlines"

관련 정보