텍스트 파일에서 임의의 줄을 표시하는 방법?


26

쉘 스크립트를 작성하려고합니다. 아이디어는 텍스트 파일에서 무작위로 한 줄을 선택하여 우분투 데스크탑 알림으로 표시하는 것입니다.

그러나 스크립트를 실행할 때마다 다른 줄을 선택하고 싶습니다. 이를위한 해결책이 있습니까? 전체 스크립트를 원하지 않습니다. 그 단순한 것만.



답변:


40

shuf유틸리티를 사용 하여 파일에서 임의의 줄을 인쇄 할 수 있습니다

$ shuf -n 1 filename

-n : 인쇄 할 줄 수

예 :

$ shuf -n 1 /etc/passwd

git:x:998:998:git daemon user:/:/bin/bash

$ shuf -n 2 /etc/passwd

avahi:x:84:84:avahi:/:/bin/false
daemon:x:2:2:daemon:/sbin:/bin/false

그러나 이것을 사용하면 수동으로 n 값을 변경해야합니까? 해당 쉘이 자동으로 다른 줄을 임의로 선택하고 싶습니다. 정확히 무작위 일 필요는 없습니다. 그러나 다른 라인.
Anandu M Das

4
@AnanduMDas 아니요 n인쇄 할 줄 수 를 나타내지 않아도됩니다 . (즉, 한 줄 또는 두 줄만 원하는지 여부). 줄 번호가 아닙니다 (즉, 첫 번째 줄 두 번째 줄).
aneeshep

@AnanduMDas : 내 대답에 몇 가지 예를 추가했습니다. 지금 분명해 지길 바랍니다.
aneeshep

1
u를 지금 분명히 고맙습니다 :) 또한 다른 알고리즘을 찾았습니다. 현재 시간 (초 만 date +%S)을 변수 x에 저장 한 다음 텍스트 파일 의 headand tail명령을 사용하여 x 번째 줄을 선택 하십시오. 어쨌든 방법이 더 쉽습니다. 감사합니다
Anandu M Das

+1 : shuf은 coreutils에 있으므로 기본적으로 사용 가능합니다. 참고 : 입력 파일을 메모리에로드합니다. 이를 필요로하지 않는 효율적인 알고리즘이 있습니다 .
jfs


8

그냥 재미를 위해, 여기입니다 순수 bash는 솔루션을 사용하지 않는 shuf, sort, wc, sed, head, tail또는 기타 외부 도구를 제공합니다.

shuf변형에 비해 유일한 장점 은 순수한 배쉬이기 때문에 약간 빠릅니다. 내 컴퓨터에서 1000 줄의 파일의 경우 shuf변형은 약 0.1 초가 걸리고 다음 스크립트는 약 0.01 초가 걸립니다.) shuf가장 쉽고 짧은 변형이지만 빠릅니다.

모든 정직에서 나는 shuf고효율이 중요한 관심사가 아니라면 여전히 해결책을 원할 것 입니다.

#!/bin/bash

FILE=file.txt

# get line count for $FILE (simulate 'wc -l')
lc=0
while read -r line; do
 ((lc++))
done < $FILE

# get a random number between 1 and $lc
rnd=$RANDOM
let "rnd %= $lc"
((rnd++))

# traverse file and find line number $rnd
i=0
while read -r line; do
 ((i++))
 [ $i -eq $rnd ] && break
done < $FILE

# output random line
printf '%s\n' "$line"

@EliahKagan 제안과 좋은 점에 감사드립니다. 나는 너무 많은 생각을하지 않은 꽤 많은 코너 사례가 있음을 인정할 것입니다. 나는 이것의 재미를 위해 이것을 정말로 썼습니다. shuf어쨌든 사용하는 것이 훨씬 좋습니다. 그것을 생각할 때, shuf나는 이전에 쓴 것처럼 순수한 배쉬가 실제로 사용하는 것보다 효율적이라고 생각하지 않습니다 . 외부 도구를 실행할 때 가장 작은 (일정한) 오버 헤드가있을 수 있지만 해석 된 bash보다 더 빨리 실행됩니다. 그래서 shuf확실히 더 나은 확장 할 수 있습니다. 스크립트가 교육적인 목적에 부합한다고 가정 해 봅시다. 그것이 가능하다는 것을 알게되어 기쁩니다.)
Malte Skoruppa

GNU / Linux / Un * x는 순전히 학문적 인 운동이 아니라면 다시 발명하고 싶지 않은 도로 테스트를 거친 휠을 많이 가지고 있습니다. "쉘"은 입 / 출력 및 다양한 옵션을 통해 다양한 방식으로 (재) 조립 될 수있는 기존의 많은 부품을 조립하는 데 사용되었습니다. 스포츠 용이 아닌 한 (예 : codegolf.stackexchange.com/tour ) 다른 형식은 나쁜 것입니다 .
michael

2
@michael_n "순수한 배쉬"방식은 주로 다른 작업을 가르치고 수정하는 데 유용하지만, 이것이 보이는 것보다 "실제적인"구현에 더 합리적입니다. Bash는 광범위하게 사용 가능하지만 shufGNU Coreutils에 따라 다릅니다 (예 : FreeBSD 10.0에는 해당되지 않음). sort -R이식성이 있지만 다른 (관련된) 문제를 해결합니다. 여러 줄로 표시되는 문자열은 한 번만 나타나는 확률과 같습니다. (물론, wc다른 유틸리티도 여전히 사용할 수 있습니다.) 여기서 가장 큰 한계는 32768 번째 줄 이후에는 아무 것도 선택하지 않는다는 것입니다.
Eliah Kagan

2
Malte Skoruppa : bash PRNG 질문을 U & L 로 옮겼 습니다 . 시원한. 힌트 : $((RANDOM<<15|RANDOM))0..2 ^ 30-1입니다. @JFSebastian 보다 빈번한 입력으로 기울어지는 shuf것은 아닙니다 sort -R. 그 shuf -n 1자리에 놓고 sort -R | head -n1비교하십시오. 참조 (Btw는 10 ^ 3 반복은. 차이를 보여 매우 아직 충분히 빠른 10 ^ 6 인) 시각적 데모, 거친어리 석음의이 비트는 모든 문자열 고주파있는 큰 입력에서 작동 보여 .
Eliah Kagan

1
@JFSebastian이 명령에서 입력은 dieharder모두 0 인 것 같습니다. 이것이 내 입장에서 이상한 실수가 아니라고 가정하면 왜 그것이 무작위가 아닌지 설명 할 것입니다! while echo $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 )); do :; done | perl -ne 'print pack "I>"' > out잠시 동안 실행 한 다음 out16 진 편집기 를 사용 하여 내용을 검사 하면 멋진 데이터를 얻 습니까? (또는 그러나 다른 사람처럼 볼 수 있습니다.) 나는 모든 0을 얻고, RANDOM범인되지 않습니다 : 내가 교체 할 때 나는 모든 0을 얻을 $(( RANDOM << 17 | RANDOM << 2 | RANDOM >> 13 ))100너무.
Eliah Kagan

4

파일이 있다고 가정 해보십시오 notifications.txt. 랜덤 생성기의 범위를 결정하기 위해 총 라인 수를 계산해야합니다.

$ cat notifications.txt | wc -l

변수에 쓸 수 있습니다.

$ LINES=$(cat notifications.txt | wc -l)

이제부터 숫자를 생성 0하기 $LINE위해 RANDOM변수 를 사용 합니다.

$ echo $[ $RANDOM % LINES]

변수에 쓸 수 있습니다.

$  R_LINE=$(($RANDOM % LINES))

이제이 줄 번호 만 인쇄하면됩니다 :

$ sed -n "${R_LINE}p" notifications.txt

RANDOM 소개 :

   RANDOM Each time this parameter is referenced, a random integer between
          0 and 32767 is generated.  The sequence of random numbers may be
          initialized by assigning a value to RANDOM.  If RANDOM is unset,
          it  loses  its  special  properties,  even if it is subsequently
          reset.

파일의 행 번호가 32767 미만인지 확인하십시오. 참조 당신이 상자 밖으로 작동 더 큰 무작위로 생성이 필요합니다.

예:

$ od -A n -t d -N 3 /dev/urandom | tr -d ' '

문체 대안 (bash) :LINES=$(wc -l < file.txt); R_LINE=$((RANDOM % LINES)); sed -n "${R_LINE}p" file.txt
michael


예를 들어, 회색 비트 맵사용하여 Test PRNG 의 마지막 그림 % n을보고 난수 에 적용하는 것이 좋지 않은 이유를 이해하십시오 .
jfs

2

입력 파일 또는 stdin에서 임의의 행을 선택하는 Python 스크립트는 다음과 같습니다.

#!/usr/bin/env python
"""Usage: select-random [<file>]..."""
import random

def select_random(iterable, default=None, random=random):
    """Select a random element from iterable.

    Return default if iterable is empty.
    If iterable is a sequence then random.choice() is used for efficiency instead.
    If iterable is an iterator; it is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    try:
        return random.choice(iterable) # O(1) time and space
    except IndexError: # empty sequence
        return default
    except TypeError: # not a sequence
        return select_random_it(iter(iterable), default, random.randrange)

def select_random_it(iterator, default=None, randrange=random.randrange):
    """Return a random element from iterator.

    Return default if iterator is empty.
    iterator is exhausted.
    O(n)-time, O(1)-space algorithm.
    """
    # from /programming//a/1456750/4279
    # select 1st item with probability 100% (if input is one item, return it)
    # select 2nd item with probability 50% (or 50% the selection stays the 1st)
    # select 3rd item with probability 33.(3)%
    # select nth item with probability 1/n
    selection = default
    for i, item in enumerate(iterator, start=1):
        if randrange(i) == 0: # random [0..i)
            selection = item
    return selection

if __name__ == "__main__":
    import fileinput
    import sys

    random_line = select_random_it(fileinput.input(), '\n')
    sys.stdout.write(random_line)
    if not random_line.endswith('\n'):
        sys.stdout.write('\n') # always append newline at the end

알고리즘은 O (n) 시간, O (1) 공간입니다. 32767 라인보다 큰 파일에서 작동합니다. 입력 파일을 메모리에로드하지 않습니다. 각 입력 행을 정확히 한 번 읽습니다. 즉, 임의의 큰 (그러나 유한 한) 컨텐츠를 파이프에 파이프 할 수 있습니다. 다음 은 알고리즘에 대한 설명입니다 .


1

나는 Malte Skoruppa와 다른 사람들이 한 일에 깊은 인상을 받았지만, 여기에 그것을하는 훨씬 더 간단한 "순수한 배쉬"방법이 있습니다 :

IFS=$'\012'
# set field separator to newline only
lines=( $(<test5) )
# slurp entire file into an array
numlines=${#lines[@]}
# count the array elements
num=$(( $RANDOM$RANDOM$RANDOM % numlines ))
# get a (more-or-less) random number within the correct range
line=${lines[$num]}
# select the element corresponding to the random number
echo $line
# display it

일부 사람들이 언급했듯이 $ RANDOM은 무작위가 아닙니다. 그러나 필요에 따라 $ RANDOM을 함께 묶으면 32767 행의 파일 크기 제한을 극복 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.