dotglob의 현재 값을 사용합니다.

dotglob의 현재 값을 사용합니다.

저는 bash 프롬프트 PS1에서 작업 중이며 현재 디렉토리의 파일 수를 인쇄하고 싶습니다.

작동하는 코드를 작성했는데
이 (중복) 스크립트를 단순화할 수 있는 방법이 있습니까?

$(ls -l | grep ^- | wc -l) $(if [ $(ls -l | grep ^- | wc -l) -eq 1 ]; then echo "file"; else echo "files"; fi)

내 목적은 폴더의 파일 수를 인쇄하고만약에선언해야 함다루다복수형은 두 가지 상황을 관리합니다.

  • 파일 1개
  • 파일 2개

예를 들어, my ~ 폴더에는 세 개의 파일이 포함되어 있으므로 스크립트는 "files"라는 단어를 인쇄해야 합니다. my ~/Desktop에는 파일이 하나만 있으므로 스크립트는 "files"를 인쇄해야 합니다.

위의 코드 줄을 작성하고 작업이 완료되었지만 더 깔끔하고 스마트한 방법이 있다고 생각했습니다.

답변1

첫째, 더 빠른 명령을 사용하여 올바른 번호를 찾을 수 있습니다. 내 테스트에서는 이것이 find . -maxdepth 1 -type f -not -name '.*' | wc -l(약 4:3)보다 빠른 것으로 나타났습니다. ls -l | grep '^-' | wc -l파일도 숨기려면 찾기 부분을 사용하거나 ls -a생략 해야 합니다.-not -name '.*'

다음으로, 파일을 두 번 계산하는 대신 결과를 변수에 저장하고 재사용합니다.

$(count=$(find . -maxdepth 1 -type f -not -name '.*' | wc -l)
  if [[ "$count" -eq 1 ]]; then
    echo "1 file"
  else
    echo "$count files"
  fi)

보시다시피, 서브셸을 사용해야 합니다. 그렇지 않으면 두 번째 명령에서 변수를 사용할 수 없습니다. 나는 또한 여기서 [[ ... ]]대신 bash 특정 것을 사용했습니다 [ ... ]. 일반적으로 말하면 이것이 더 나은 솔루션입니다.

마지막으로 Bash 변수를 사용하여 $PROMPT_COMMAND프롬프트를 인쇄하기 전에 일부 코드를 실행할 수도 있습니다. $PS1이렇게 하면 모든 코드를 변수에 저장할 필요가 없습니다 $PS1. 변수 $count는 다음과 같습니다.글로벌하지만. 다음과 같이 보일 수 있습니다:

function count_files () {
  __count=$(find . -maxdepth 1 -type f -not -name '.*' | wc -l)
  if [[ "$__count" -eq 1 ]]; then
    __plural=
  else
    __plural=s
  fi
}
PROMPT_COMMAND=count_files
PS1='other stuff $__count file$__plural more stuff'

그러나 이러한 단계 중 어떤 단계를 단순화하는 것으로 간주하는지 확실하지 않습니다. :)

편집하다

방금 알아냈어이 스레드. 다음과 같이 파일 수를 계산하는 데 사용할 수 있습니다.

function count_files () {
  local name
  __count=0
  for name in *; do
    [[ -f "$name" ]] && ((__count++))
  done
  # like above ...
}

찾기 버전보다 약 1:13 정도 빠릅니다. 이는 더 적은 수의 프로세스를 시작하기 때문에 남성적입니다(find 버전이 ls 버전보다 빠른 이유와 동일). 트레드에는 다른 extglob 솔루션이 있습니다. 그것은 모두 당신이 파일을 원하는지, 파일에 대한 링크를 원하는지에 따라 달라집니다. 다시 말하지만, 코드는 더 빨라졌지만 이제는 더 복잡해 보입니다. 그렇다면 어떤 종류의 "단순성"을 목표로 하시나요?

편집 2

내가 댓글에서 말한 내용에도 불구하고 프로세스를 시작하는 오버헤드는 정렬해야 하는 경우 정렬하는 오버헤드에 비해 아마도 작을 것입니다.많은/usr/bin/파일의 경우 약 2500개의 파일이 포함된 코드를 실행하면 표시되기 시작합니다.

답변2

스크립트를 단순화하는 첫 번째 방법은 파일 수를 변수에 저장하여 다시 계산할 필요가 없도록 하는 것입니다.

현재 디렉터리의 파일 수를 찾는 다른 방법:

n=$(ls -l | grep -c ^-)

여기서 단순화는 grep의 -c옵션을 사용하여 일치 항목을 계산하는 것입니다. 일치 항목을 잘못 계산할 위험이 있습니다.ls의 출력을 구문 분석합니다.라는 파일이 있으면 $'some\n-file'줄 시작 부분에 하이픈을 넣는 것처럼 보입니다.

n=$(stat -c %F -- * | grep -c 'regular .*file')

grep은 .*"일반 파일"과 "일반 빈 파일"이 일치하는 것으로 간주합니다. 이것통계자료*이 명령은 쉘 glob이 피하는 각 파일의 유형을 출력합니다 ls.

bash에 익숙하지만 들어본 적이 있다면zsh와 강력한 파일 이름 글로빙 구문, 다음을 수행할 수 있습니다.

n=$(zsh -c 'a=( *(.) ); echo ${#a}')

a그 안에 확장자가 "Pure Files"인 파일 목록으로만 필터링된 파일 로 채워지는 배열을 만듭니다 .*.

복소수를 올바르게 인쇄하려면 Case 문을 고려하세요.

case $n in
(1) printf "1 file";;
(*) printf "$n files";;
esac

Case 문은 파일 개수에 따라 서로 다른 메시지를 인쇄하려는 경우 더 큰 유연성을 제공합니다(예: "파일 없음!").

더 간단하게는 다음과 같은 조건을 고려해보세요.

[[ $n == 1 ]] && printf "1 file" || printf "$n files"

마지막으로 Steeldriver 권장 사항에 대해 논의합니다.

n=$( files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))
printf "%d file%s" "$n" "$(test "$n" -ne 1 && echo s)"

n이는 (결국) 명령 대체를 활성화하여 값을 할당합니다. 임시 명령 대체에서는 files모든 항목과 dirs디렉터리에만 해당하는 두 개의 배열을 만들었습니다. 명령 대체의 마지막 작업은 둘 사이의 차이점을 보고하는 것입니다. 그런 다음 printf적절한 복수형 접미사와 함께 파일 수를 인쇄합니다.

이것은 가지고 있는 모든 설정을 사용합니다 dotglob. 도트 파일을 강제로 계산(또는 생략)하려면 dotglob명령 대체에서 설정하거나 설정을 해제해야 합니다.

dotglob의 현재 값을 사용합니다.

n=$( files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))

현재 도트 글로브에 관계없이 도트 파일을 강제로 계산합니다.

n=$(shopt -s dotglob;
    files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))

구현하다아니요현재 dotglob에 관계없이 도트 파일 수:

n=$(shopt -u dotglob;
    files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))

답변3

find . -maxdepth 1 -type f | awk 'END {printf("%d %s%s\n", NR, "file", (NR > 1) ? "s" : "")}'

설명하다:

  1. find . -maxdepth 1 -type f- 현재 디렉토리의 모든 파일을 검색합니다.
  2. awk 'END {printf("%d %s%s\n", NR, "file", (NR > 1) ? "s" : "")}'
    • NR= 프로그램으로부터 수신된 라인 수 find, 파일 수와 같습니다.
    • (NR > 1) ? "s" : "")- 라인(파일) 개수가 1개를 초과하는 경우 s단어 끝에 끝을 추가합니다 file.

답변4

방금 이걸 만들었어요. ls파일 이름에 이상한 문자(예: 개행 문자)가 있으면 출력이 손상될 수 있기 때문에 경로 이름 확장을 사용 하지 않았습니다 .

#!/usr/bin/env bash

((n=0))

for i in *; do
  [[ -f "${i}" ]] && ((n++))
done

((n==1)) && { echo "${n} file"; exit; }
echo "${n} files"

관련 정보