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를 쉽게 추가하면 shell
sed가 전혀 적용되지 않습니다.
tr
sed에는 비슷한 기능이 있습니다 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 소개.
기타 참고자료:
- 조회 테이블
- seder 패킷 캡처 튜토리얼SF 익스프레스를 입력하세요.
- 태그(브랜치)에 대한 추가 정보입니다.
좀 더 하드코어한 내용을 보려면 다음을 참조하세요.
- Greg Ubben의 sed dc. 와짧은 설명.
- 테트리스~이 동반되다배쉬 래퍼.
행운을 빌어요.