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つのフィールドは変更されないため、すべて1つのグループに配置でき、最後のフィールドの先頭も変更されません。

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

また、フィールド数は固定され、シェルは最後のフィールドなので、フィールド数を数える必要はありません。したがって、私たちはこのプログラムをよりシンプルで明確な方法で書くことができます。

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

あるいは、^行の最後の部分だけを削除して置き換えることもできます。

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

これらの一時的な単純化により、正規表現をより明確にし、意図を明確にすることができません。

特定の基準を満たす回線で作業する必要がある場合は、2つの基本的なアプローチがあります。 1つは、上記のようにライン全体を一致させ、グループ化を使用して複数の部分に分割することです。別のアプローチは、s特定のパターンに一致する行にコマンドを制限することです。第2のアプローチは、条件がパターン置換と直接関係しない場合、より読みやすくなる。以下は、この原則に基づく例です。デフォルトのシェルがbashの各ユーザーに対して、そのグループを2981に変更します。

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

複数の置換を実行するには、複数のコマンドを使用できます。各コマンドをoptionsに引数として渡します-e。 (ほとんどのsed実装では、改行によってコマンドの個々のパラメータを区別することもできます。一部の実装ではセミコロンをコマンド区切り文字として受け入れることもあります。)これらのコマンドは各行に順番に適用されるため、最初のコマンドの結果は次のようになります。と同じです。 2番目のコマンドが一致します。

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に埋めることです。それは2番目の部分になるでしょう。 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をラップするときに利用できる1つのことは、次のように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ラベルを指定できます。木の枝、2つの機能に基づいてこれらにジャンプします。

: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
}

これは最良の例ではありませんが、あなたがアイデアを得たことを願っています。

2番目の方法は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について。

その他の参考資料:

もっとハードコアなものについては、以下をご覧ください。


頑張ってください。

関連情報