Programming

파일의 각 줄에 대해 명령을 어떻게 실행합니까?

procodes 2020. 6. 27. 14:50
반응형

파일의 각 줄에 대해 명령을 어떻게 실행합니까?


예를 들어, 지금은 유닉스 경로를 파일에 쓴 두 개의 파일을 변경하기 위해 다음을 사용하고 있습니다.

cat file.txt | while read in; do chmod 755 "$in"; done

더 우아하고 안전한 방법이 있습니까?


파일을 한 줄씩 읽고 명령을 실행하십시오.

답변이 하나뿐이 아니기 때문에 ...

  1. shell 명령 줄 확장
  2. xargs 전용 도구
  3. while read 약간의 언급과 함께
  4. while read -u대화식 처리를 fd위해 전용 사용 (샘플)

영업 요청에 대해서 : 실행 chmod파일에 나열된 모든 대상에하는 것은 , xargs지정된 도구입니다. 그러나 다른 응용 프로그램, 소량의 파일 등의 경우 ...

  1. 전체 파일을 명령 행 인수로 읽습니다.

    파일이 너무 크지 않고 모든 파일의 이름 이 공백이나 따옴표와 같은 다른 특수 문자없이 잘 지정되어 있으면 shell명령 줄 확장을 사용할 수 있습니다 . 간단히:

    chmod 755 $(<file.txt)
    

    적은 양의 파일 (줄)의 경우이 명령이 더 밝습니다.

  2. xargs 올바른 도구입니다

    더 많은 양의 파일 또는 입력 파일의 거의 모든 라인 수 ...

    많은 바이너리 유틸리티의 도구, 등 chown, chmod, rm, cp -t...

    xargs chmod 755 <file.txt
    

    당신은 특수 문자 및 / 또는 라인의 많은 경우 file.txt.

    xargs -0 chmod 755 < <(tr \\n \\0 <file.txt)
    

    항목별로 명령을 정확히 1 회 실행해야하는 경우 :

    xargs -0 -n 1 chmod 755 < <(tr \\n \\0 <file.txt)
    

    chmod여러 파일을 인수 허용하므로이 샘플에서는 필요하지 않지만 질문 제목과 일치합니다.

    특별한 경우에는 다음에 의해 생성 된 명령에서 파일 인수의 위치를 ​​정의 할 수도 있습니다 xargs.

    xargs -0 -I '{}' -n 1 myWrapper -arg1 -file='{}' wrapCmd < <(tr \\n \\0 <file.txt)
    

    seq 1 5입력으로 테스트

    이 시도:

    xargs -n 1 -I{} echo Blah {} blabla {}.. < <(seq 1 5)
    Blah 1 blabla 1..
    Blah 2 blabla 2..
    Blah 3 blabla 3..
    Blah 4 blabla 4..
    Blah 5 blabla 5..
    

    commande는 한 줄에 한 번 수행됩니다 .

  3. while read 및 변형.

    OP가 제안한 cat file.txt | while read in; do chmod 755 "$in"; done대로 작동하지만 두 가지 문제가 있습니다.

    • cat |입니다 쓸모 포크 ,와

    • | while ... ;done이후 환경이 사라지는 서브 쉘 이 될 ;done입니다.

    그래서 이것은 더 잘 쓰여질 수 있습니다 :

    while read in; do chmod 755 "$in"; done < file.txt
    

    그러나,

    • 경고 $IFSread플래그 가 표시 될 수 있습니다 .

      help read
      
      read: read [-r] ... [-d delim] ... [name ...]
          ...
          Reads a single line from the standard input... The line is split
          into fields as with word splitting, and the first word is assigned
          to the first NAME, the second word to the second NAME, and so on...
          Only the characters found in $IFS are recognized as word delimiters.
          ...
          Options:
            ...
            -d delim   continue until the first character of DELIM is read, 
                       rather than newline
            ...
            -r do not allow backslashes to escape any characters
          ...
          Exit Status:
          The return code is zero, unless end-of-file is encountered...
      

      경우에 따라 사용해야 할 수도 있습니다

      while IFS= read -r in;do chmod 755 "$in";done <file.txt
      

      이상한 파일 이름 문제를 피하기 위해. 그리고 어쩌면 당신이 문제를 격려한다면 UTF-8:

      while LANG=C IFS= read -r in ; do chmod 755 "$in";done <file.txt
      
    • While you use STDIN for reading file.txt, your script could not be interactive (you cannot use STDIN anymore).

  4. while read -u, using dedicated fd.

    Syntax: while read ...;done <file.txt will redirect STDIN to file.txt. That mean, you won't be able to deal with process, until they finish.

    If you plan to create interactive tool, you have to avoid use of STDIN and use some alternative file descriptor.

    Constants file descriptors are: 0 for STDIN, 1 for STDOUT and 2 for STDERR. You could see them by:

    ls -l /dev/fd/
    

    or

    ls -l /proc/self/fd/
    

    From there, you have to choose unused number, between 0 and 63 (more, in fact, depending on sysctl superuser tool) as file descriptor:

    For this demo, I will use fd 7:

    exec 7<file.txt      # Without spaces between `7` and `<`!
    ls -l /dev/fd/
    

    Then you could use read -u 7 this way:

    while read -u 7 filename;do
        ans=;while [ -z "$ans" ];do
            read -p "Process file '$filename' (y/n)? " -sn1 foo
            [ "$foo" ]&& [ -z "${foo/[yn]}" ]&& ans=$foo || echo '??'
        done
        if [ "$ans" = "y" ] ;then
            echo Yes
            echo "Processing '$filename'."
        else
            echo No
        fi
    done
    

    To close fd/7:

    exec 7<&-            # This will close file descriptor 7.
    ls -l /dev/fd/
    

Yes.

while read in; do chmod 755 "$in"; done < file.txt

This way you can avoid a cat process.

cat is almost always bad for a purpose such as this. You can read more about Useless Use of Cat.


if you have a nice selector (for example all .txt files in a dir) you could do:

for i in *.txt; do chmod 755 "$i"; done

bash for loop

or a variant of yours:

while read line; do chmod 755 "$line"; done <file.txt

If you know you don't have any whitespace in the input:

xargs chmod 755 < file.txt

If there might be whitespace in the paths, and if you have GNU xargs:

tr '\n' '\0' < file.txt | xargs -0 chmod 755

If you want to run your command in parallel for each line you can use GNU Parallel

parallel -a <your file> <program>

Each line of your file will be passed to program as an argument. By default parallel runs as many threads as your CPUs count. But you can specify it with -j


I see that you tagged bash, but Perl would also be a good way to do this:

perl -p -e '`chmod 755 $_`' file.txt

You could also apply a regex to make sure you're getting the right files, e.g. to only process .txt files:

perl -p -e 'if(/\.txt$/) `chmod 755 $_`' file.txt

To "preview" what's happening, just replace the backticks with double quotes and prepend print:

perl -p -e 'if(/\.txt$/) print "chmod 755 $_"' file.txt

You can also use AWK which can give you more flexibility to handle the file

awk '{ print "chmod 755 "$0"" | "/bin/sh"}' file.txt

if your file has a field separator like:

field1,field2,field3

To get only the first field you do

awk -F, '{ print "chmod 755 "$1"" | "/bin/sh"}' file.txt

You can check more details on GNU Documentation https://www.gnu.org/software/gawk/manual/html_node/Very-Simple.html#Very-Simple


The logic applies to many other objectives. And how to read .sh_history of each user from /home/ filesystem? What if there are thousand of them?

#!/bin/ksh
last |head -10|awk '{print $1}'|
 while IFS= read -r line
 do
su - "$line" -c 'tail .sh_history'
 done

Here is the script https://github.com/imvieira/SysAdmin_DevOps_Scripts/blob/master/get_and_run.sh

참고URL : https://stackoverflow.com/questions/13939038/how-do-you-run-a-command-for-each-line-of-a-file

반응형