sed를 사용하여 passwd 파일 처리

sed를 사용하여 passwd 파일 처리

sed를 배우려고 하는데 고민이 많습니다. 내가 원하는 것은 다음을 수행하기 위해 sed 명령과 함께 bash 스크립트를 사용하여 내 passwd 파일을 처리하는 것입니다. 그룹 ID가 20000인 각 사용자에 대해 파일의 GID를 2000x로 바꾸십시오. 여기서 x는 첫 번째입니다. 사용자의 사용자 이름 알파벳 순서로(예: a는 1, b는 2 등) 또한 기본 셸이 bash인 각 사용자의 경우 그룹을 bash로 변경하고, tcsh 셸을 사용하는 각 사용자의 경우 해당 그룹을 tcshgroup으로 변경합니다. 나는 awk에서 위의 작업을 수행했지만(사용하기 더 쉽다고 생각함) sed를 어디서 시작해야 할지조차 모릅니다. 어떤 도움이라도 대단히 감사하겠습니다.

이것은 passwd 파일의 일부입니다:

speech-dispatcher:x:108:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/sh
colord:x:109:117:colord colour management daemon,,,:/var/lib/colord:/bin/false
lightdm:x:110:118:Light Display Manager:/var/lib/lightdm:/bin/false
avahi:x:111:120:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
hplip:x:112:7:HPLIP system user,,,:/var/run/hplip:/bin/false
pulse:x:113:121:PulseAudio daemon,,,:/var/run/pulse:/bin/false
saned:x:114:20000::/home/saned:/bin/tcsh
mmccormick:x:1000:20000:owner,,,:/home/mmccormick:/bin/bash

이상적으로는 각 줄의 필드 4를 선택하여 쉘의 그룹 ID와 필드 7을 가져오지만 다시 sed에서 이를 수행하는 방법을 모르겠습니다. 미리 감사드립니다.

답변1

awk는 여기서 정말 자연스러운 도구입니다. /etc/passwd콜론으로 구분된 필드로 구성되고 각 줄은 동일한 레이아웃을 가지며, 이는 awk가 구문 분석을 위해 만들어진 것입니다.

sed를 사용하려는 경우 기본 아이디어는 대괄호 그룹의 각 필드를 캡처하고 역참조를 사용하여 각 필드의 내용을 참조하는 것입니다. 예를 들어 사용자의 셸을 zsh(이전의 bash)로 변경하는 방법은 다음과 같습니다.

sed 's~^\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):/bin/bash$~\1:\2:\3:\4:\5:\6:/bin/zsh~'

구분자 로 주로 사용하는데 ~, 다른 문자를 사용해도 되고, /패턴에 나타날 때는 다른 문자를 사용하는 것이 더 편리합니다. 또는 일반적인 선택입니다. 정규식에서 특별한 의미를 갖는 문자를 선택하지 마십시오./~#!

이 정규식은 6번을 포함하며 \([^:]*\):필드( 를 제외한 일련의 문자 :) 및 필드 구분 기호와 일치합니다. 편의상 각 필드를 별도의 그룹에 넣습니다. 처음 6개 필드는 변경되지 않으므로 모두 하나의 그룹에 넣을 수 있으며 마지막 필드의 시작 부분도 변경되지 않습니다.

sed 's~^\([^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:/bin/\)bash$~\1zsh~'

또한 필드 개수는 고정되어 있고 shell이 ​​마지막 필드이므로 필드 개수를 셀 필요가 없습니다. 따라서 우리는 이 프로그램을 더 간단하지만 덜 명확한 방식으로 작성할 수 있습니다.

sed 's~^\(.*:/bin/\)bash$~\1zsh~'

또는 ^줄의 마지막 부분만 삭제하고 바꿀 수도 있습니다.

sed 's~:/bin/bash$~:/bin/zsh~'

이러한 임시 단순화를 통해 정규식을 더 명확하게 만들면서 의도를 덜 명확하게 만들 수 있습니다.

특정 기준을 충족하는 회선에서 작업해야 하는 경우 두 가지 기본 접근 방식이 있습니다. 하나는 위에서 했던 것처럼 전체 라인을 일치시키고 그룹화를 사용하여 여러 부분으로 분할하는 것입니다. 또 다른 접근 방식은 s특정 패턴과 일치하는 줄로 명령을 제한하는 것입니다. 두 번째 접근 방식은 조건이 패턴 교체와 직접적인 관련이 없을 때 더 읽기 쉽습니다. 다음은 이 원칙에 기초한 예입니다. 기본 셸이 bash인 각 사용자에 대해 해당 그룹을 2981로 변경합니다.

sed '/:\/bin\/bash$/ s~^\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\)$~\1:\2:\3:2981:\5:\6:\7'

여러 대체를 수행하려면 여러 명령을 사용할 수 있습니다. 각 명령을 options 에 인수로 전달합니다 -e. (대부분의 sed 구현에서는 줄 바꿈을 통해 명령의 개별 매개변수를 구분할 수도 있습니다. 일부 구현에서는 세미콜론을 명령 구분 기호로 허용하기도 합니다.) 이러한 명령은 각 줄에 차례로 적용되므로 첫 번째 명령의 결과는 다음과 같습니다. 두 번째 명령이 일치합니다.

sed -e '/:\/bin\/bash$/ s~^\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\)$~\1:\2:\3:2981:\5:\6:\7' \
    -e '/:\/bin\/tcsh$/ s~^\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\)$~\1:\2:\3:1989:\5:\6:\7' \

답변2

@Gilles는 할 말이 많습니다. 다음은 몇 가지 추가 고려 사항입니다.

sed스트림(들)스트림(ed) 작성자입니다. 소개 부분을 읽어보세요 위키피디아. 중요한 부분은 이렇습니다패턴 공간등.

대부분의 경우 정규식을 알고 있다고 가정하고 이에 대해 자세히 설명하지 않겠습니다.


좀 길어지긴 했지만 괜찮습니다.

이것"단순한"셸과 비교하면 일부는 사용자의 GID를 대체합니다. 이것이 첫 번째 부분입니다. 더 흥미로운 부분은 계정/사용자 이름의 첫 글자를 번역하여 GID에 채우는 것입니다. 그건 두 번째 부분이 될 거야 sed - 조회 테이블아래 - 끝목록 6어느 정도 기능적인 프로그램이 있습니다.GID의 문자를 숫자로.

이 중 다수가 나타날 수 있습니다."아 왜요?"– 그러나 그것은 좋은 개념 연습이었습니다(더 나은 단어가 부족하여).


1부: 셸을 통해 GID 교환

cut, IFS 또는 기타 "더 쉬운" 방법 대신 sed를 사용하여 명명된 그룹의 GID를 가져오는 함수를 추가할 수 있습니다.

#!/bin/bash

get_gnr()
{
    # -n    Do not print unless I say so.
    # s///  Substitute lines beginning with argv 1:
    # p     Print if there was a substitution.
    # $1    Arg 1 to bash function.
    sed -n 's/^'$1':[^:]*:\(.*\):/\1/p' /etc/group
}

# Assign what ever get_gnr() prints to gr_pulse
gnr_bash=$(get_gnr "bash")
gnr_tcsh=$(get_gnr "tcsh")

printf "Group %5s = %d\n" "bash" "$gnr_bash"
printf "Group %5s = %d\n" "tcsh" "$gnr_tcsh"

더 많은 오류 검사를 수행해야 합니다. 예를 들어 실제로 bash라는 그룹이 있는지 테스트해 보세요.

그런 다음 첫 번째 알파를 꼬리로 변환하려는 GID를 저장하기 위해 몇 가지 변수가 필요할 수 있습니다.그러나 작업 설명을 보면 이 작업을 bash/tcsh 그룹 전환 이전에 수행해야 하는지, 아니면 이후에 수행해야 하는지 명확하지 않습니다.

그래도. bash 스크립트로 sed를 래핑하는 경우 활용할 수 있는 한 가지는 다음을 통해 bash 변수를 사용하는 것입니다.일시적으로sed를 탈출하세요. 또한 awk와 마찬가지로 sed 명령을 그룹화할 수 있습니다. 예를 들면 다음과 같습니다.

/pattern/ { exec if match}
/pattern/ ! { exec if no match }

여기 내가 의미하는 바를 보여주는 예가 있습니다. 하지만 이 특정 예에서는 약간 중복됩니다. 또한 몇 가지 추가 출력을 추가했는데, 이는 현재 진행 중인 작업을 명확하고 빠르게 확인하기 위해 작성할 때 유용합니다.

gid_tr_to_uname=121

sed '
/:\/bin\/bash$/ {
    # Add an arrow only to visualize that line has changed
    s/^/--> /p
    # Susbtitute group
    s/\(^[^:]*:[^:]*:[^:]*:\)\([^:]*\)/\1'$gnr_bash'/
}
/:\/bin\/tcsh$/ {
    # Add an arrow only to visualize that line has changed
    s/^/--> /p
    # Susbtitute group
    s/\(^[^:]*:[^:]*:[^:]*:\)\([^:]*\)/\1'$gnr_tcsh'/
}
/[^:]*:[^:]*:[^:]*:'$gid_tr_to_uname':/ {
    # Insert line to visualize change [ old/new ]
    i\
tr group alpha name [
    p
    s/a\([^:]*:[^:]*:[^:]*:[^:]*\)\([0-9]\)\(:[^:]*:[^:]*:.*\)/a\11\3/
    s/b\([^:]*:[^:]*:[^:]*:[^:]*\)\([0-9]\)\(:[^:]*:[^:]*:.*\)/b\12\3/
    s/c\([^:]*:[^:]*:[^:]*:[^:]*\)\([0-9]\)\(:[^:]*:[^:]*:.*\)/c\13\3/
    s/d\([^:]*:[^:]*:[^:]*:[^:]*\)\([0-9]\)\(:[^:]*:[^:]*:.*\)/d\14\3/
    s/e\([^:]*:[^:]*:[^:]*:[^:]*\)\([0-9]\)\(:[^:]*:[^:]*:.*\)/e\15\3/
    s/f\([^:]*:[^:]*:[^:]*:[^:]*\)\([0-9]\)\(:[^:]*:[^:]*:.*\)/f\16\3/
    s/g\([^:]*:[^:]*:[^:]*:[^:]*\)\([0-9]\)\(:[^:]*:[^:]*:.*\)/g\17\3/
    s/h\([^:]*:[^:]*:[^:]*:[^:]*\)\([0-9]\)\(:[^:]*:[^:]*:.*\)/h\115\3/
    # ....
    # Append line to visualize end
    a\
]
}
' "$in_file"

알파물건상태가 좋지 않습니다. 아래 2부입니다.

sed 대신 bash를 사용할 수 있는 경우 IFS(awk의 FS 또는 필드 구분 기호 등)를 다음과 같이 설정하여 bash 루프에 결과를 파이핑하여 알파 변환을 단순화할 수 있습니다 :.

#      capture group 1             capture group 2
#   s (everything before gid) gid (everything after gid) trigger / \1 new gnr \2
sed \
-e 's/\(^[^:]*:[^:]*:[^:]*:\)[^:]*\(.*:\/bin\/bash$\)/\1'$gnr_bash'\2/' \
-e 's/\(^[^:]*:[^:]*:[^:]*:\)[^:]*\(.*:\/bin\/tcsh$\)/\1'$gnr_tcsh'\2/' \
"$1" |
while IFS=: read account password uid gid gecos directory shell; do
    case "$gid" in
    "$gid_tr_to_uname")
        gid=$(translate "$account" "$gid")
    esac
    printf "%s:%s:%d:%d:%s:%s:%s\n"\
        "$account" "$password" "$uid" "$gid" "$gecos" "$directory" "$shell"
done

일부 번역 기능은 다음과 같습니다.

ascii_a=$(printf "%d" "'a")
ascii_A=$(printf "%d" "'A")
translate()
{
    local first_letter="${1:0:1}"    # First character in arg 1
    local -i gid_lhs="${2:0: -1}"    # Everything but last digit in arg 2
                                     # Get ascii 10 base value / digit
    local -i ascii_val=$(printf "%d" "'$first_letter")
    local -i alphanr                 # a=1 b=2, A=27 etc
    if (( $ascii_val >= ascii_a )); then
        (( alphanr = ascii_val - ascii_a + 1 ))
    else
        (( alphanr = ascii_val - ascii_A + 27 ))
    fi
    # If you want to debug:
    # printf "[[[%s = %d => %d || %d ]]]"\
    #        "$first_letter" "$ascii_val" "$alphanr" "$gid_lhs"
    printf "%d%d" "$gid_lhs" "$alphanr"
}

case switch그러나 for를 쉽게 추가하면 shellsed가 전혀 적용되지 않습니다.

trsed에는 비슷한 기능이 있습니다 y.

sed '/0x[0-9a-zA-Z]*/ y/abcdef/ABCDEF' file

하지만 짝수 쌍이어야 하므로 a -> 1, ... p -> 16 등에 사용할 수 없습니다.


2부: sed - 조회 테이블

지금까지 계정의 이니셜을 GID에 추가하기 위해 제가 생각할 수 있는 유일한 방법은 조회 테이블을 이용하는 것입니다.

단순화하기 위해 이 작업을 단계별로 수행합니다.

목록 1

#!/bin/bash

listing1()
{
sed '
    # Pad line with lookup table
    s/$/0zero1one2two3three4four5five6six7seven8eight9nine/

    # Match something (here 1) and match it again in lookup-table
    # and grab the letters following 1 (in lookup-table) to match
    # group 2. Finally replace \1 with \2
    s/\(.\).*\1\([^0-9]*\).*/\2/
    #   |   | |     |     |   |
    #   |   | |     |     |   +----- Replace all with \2 which is "one".
    #   |   | |     |     +--------- Rest of line "2two3three4fo...".
    #   |   | |     +--------------- Match the word "one" and add it to
    #   |   | |                      group \2
    #   |   | +--------------------- Match group \1 => "1"
    #   |   |                        here in lookup-table : "1one"
    #   |   +----------------------- Match greedy => "23450zero"
    #   +--------------------------- Match one chr, that would be "1" from
    #                                input "12345\n", and add it to group \1

' < <(printf "12345\n" )
}

printf "Listing 1:\n"
listing1

결과:

Listing 1:
one

아이디어는 조회 테이블로 행을 채우고 입력의 첫 번째 일치 항목을 테이블의 해당 쌍으로 바꾸는 것입니다.

대체를 반복하여 이를 확장할 수 있습니다.

목록 2

listing2()
{
sed '
    s/$/.0zero1one2two3three4four5five6six7seven8eight9nine/
    s/\([0-9]\)\(.*\)\1\([^0-9]*\)\(.*\)/\3\2\4/
    s/\([0-9]\)\(.*\)\1\([^0-9]*\)\(.*\)/\3\2\4/
    s/\([0-9]\)\(.*\)\1\([^0-9]*\)\(.*\)/\3\2\4/
    s/\([0-9]\)\(.*\)\1\([^0-9]*\)\(.*\)/\3\2\4/
    s/\([0-9]\)\(.*\)\1\([^0-9]*\)\(.*\)/\3\2\4/
    s/\([0-9]\)\(.*\)\1\([^0-9]*\)\(.*\)/\3\2\4/
' < <(printf "12345\n" )
}

결과:

Listing 2:
onetwothreefourfive.0zero6six7seven8eight9nine

하지만 첫 번째 기간에 시작한 곳보다 별로 좋아 보이지는 않았습니다.

태그/브랜치

이것이 라벨이 작용하는 곳입니다. sed라벨을 지정할 수 있습니다.나뭇가지, 두 가지 기능을 기반으로 이들로 점프합니다.

:my_label
     s/foo/bar/
     b my_label

b로 이동하면 됩니다 my_label. 이 경우에는 영원한 순환을 의미합니다. 따라서 대부분의 경우 사용법은 다음과 같습니다.

:my_label
/\./ {          # If . exists in line
     s/#/+/     # substitute # with +
     s/\./P/    # substitute . with P
     b my_label # goto my_label
}

이것은 가장 좋은 예는 아니지만, 여러분이 아이디어를 얻으셨기를 바랍니다.

두 번째 방법은 test 또는 를 사용하는 것입니다 t. 즉, 행이 변경되면 레이블로 이동합니다.

:my_label
     s/foo/bar/   # Substitute foo with bar
     t my_label   # If there was a change aka; a substitution was done
                  # then goto my_label.

이를 통해 이전 목록을 다음과 같이 단순화할 수 있습니다. 더 즐겁게 읽을 수 있도록 여기에 쉼표를 추가했습니다.

목록 3

listing3()
{
sed '
    s/$/.0zero1one2two3three4four5five6six7seven8eight9nine/
:loop
    s/\([0-9]\)\(.*\)\1\([^0-9]*\)\(.*\)/\3,\2\4/
    t loop    # If we has a substitution goto loop

    s/,\..*// # Remove trailing comma and our lookup table rest.

' < <(printf "123458\n" )
}

결과:

Listing 3:
one,two,three,four,five,eight

우리는 알파 숫자를 원합니다. 또한 점을 구분 기호로 사용하는 것은 입력 .에 점이 포함될 수 있으므로 다소 위험할 수 있으므로 ASCII 0x7f 또는 DEL을 사용하도록 변경합니다.

예를 들어에도 작동합니다.0x00

목록 4

listing4()
{
sed '
    p # Print original line to visualize

    # Our new lookup-table:
    s/$/\x7fa1b2c3d4e5f6g7h8i9j10k11l12m13n14o15p16q17r18s19t20u21v22w23x24y25z26/
:loop
    s/\([a-z]\)\(.*\)\1\([^a-z]*\)\(.*\)/\3,\2\4/
    t loop

    s/,\x7f.*//
' < <(printf "abcdefghijklmnopqrstuvwxyz\n" )
}

결과:

Listing 4:
abcdefghijklmnopqrstuvwxyz
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26

각각이 둘 이상인 경우 다음과 같이 변경합니다.

목록 5

listing5()
{
sed '
    i\
input:
    p
    s/$/\x7fa1b2c3d4e5f6/
:loop
    s/\([a-z]\)\(.*\)\1\([^a-z]*\)\(.*\)/\3,\2\1\3\4/g
    t loop
    i\
output:
    s/,\x7f.*//
' < <(printf "aabcdefac\n" )
}

결과:

Listing 5:
input:
aabcdefac
output:
1,1,2,3,4,5,6,1,3

이제 마침내 우리의 임무에 이를 구현할 준비가 되었습니다. 예는 다음과 같습니다.

목록 6

listing6()
{
sed '
    i\
input:
    p
    s/$/\x7fa1b2c3d4e5f6g7h8i9j10k11l12m13n14o15p16q17r18s19t20u21v22w23x24y25z26/
    s/^\(.\)\([^:]*\)\(:[^:]*\)\(:[^:]*\)\(:[^:]*\)\([0-9]\)\(:.*\)\x7f.*\1\([^a-z]*\).*/\1\2\3\4\5\8\7/
    #  1alpha 2rest    3pwd      4uid      5gid    6last-digit 7rest           8number
    i\
output:
    s/,\x7f.*//
' < <(printf "master:power:110:118:Light Display Manager:/var/lib/lightdm:/bin/false\n" )
}

산출:

Listing 6:
input:
master:power:110:118:Light Display Manager:/var/lib/lightdm:/bin/false
output:
master:power:110:1113:Light Display Manager:/var/lib/lightdm:/bin/false

그게 다야.


너는 읽어야 해브루스 바넷의sed 소개.

기타 참고자료:

좀 더 하드코어한 내용을 보려면 다음을 참조하세요.


행운을 빌어요.

관련 정보