Bashのいくつかの事前ガジェットタスク

画像



こんにちはHabr!





bashでは、それを理解したように思われる状況に遭遇することがよくありますが、突然何らかの魔法が発生します。 あなたはそれを選びます、そして、私が前に疑ったことがなかったものの全体の層がまだあります...

アンダーザカット-いくつかの面白いbashパズル(平均的にも(できれば)興味深いものになります)。 教祖を驚かせるつもりはありません..しかし、あなたが猫に夢中になる前に、最初に少なくとも自分自身のためにタスクに答えることを約束してください-人/情報/グーグルなしで。



  1. タスクは簡単です。



    Helloを端末に出力するために、例から次のコマンドを取得するには、どのコマンドが必要ですか?

    $ echo "Hello" > 1
          
          





    答え
     $ cd /proc/$$/fd $ echo "Hello" > 1 Hello
          
          





    内部でどのように機能しますか?
    各プロセスの標準スレッド(STDIN、STDOUT、STDERR)の場合、ファイル記述子(0、1、2)が自動的に作成されます。

    procfsサブディレクトリ(/ proc)に移動し、/ proc / $$ (現在のプロセスのPIDを格納する特別な変数)を使用してプロセスのサブディレクトリを定義し、最後に記述子「/ proc / $$ / fd 」でサブディレクトリに移動します。 ここでの記述子は、0(stdin)、1(stdout)、2(stderr)です。 通常のキャラクターデバイスの場合と同様に作業できます。 指定されたプロセスで開かれている他のファイルの記述子がすぐに作成されます。



    スーパーユーザーは、他のユーザーのプロセス記述子に書き込み、端末にテキストを表示することもできます。



    このメカニズムにより、人気の書き込みユーティリティが機能します-ユーザーがメッセンジャーを起動せずに別のユーザーにメッセージを書き込むことができるのは、自分の端末だけです。 書き込みが別のユーザーの記述子に書き込むために、 SGIDフラグは書き込みバイナリにあります(ユーザーはttyグループに追加する必要があります)。

    同じメカニズムを使用して、システムは接続されたユーザーに再起動やその他のシステムアラートを通知します。




    2.思い出させるほどのパズルではありません。



    次のコマンドは何を出力しますか?

     $ cat /home/*/.ssh/authorized_keys
          
          





    エラーが発生しますか? 最初のファイルが表示されますか? すべてのファイルをリストしますか?

    そして、次のコマンドでどこに行きますか:

     $ cd /home/*/.ssh
          
          





    最後のコマンドの結果は次のとおりです。

     $ cp /home/*/.ssh/authorized_keys .
          
          





    答え
    誰もが正しく答えたと確信しています:

    catコマンドは、テンプレートに一致するすべてのディレクトリをバイパスして、すべてのファイルをリストします。

    cdは、テンプレートに一致する最初のディレクトリに移動します。 彼女は周りを回らず、最初のアルファベットを拾い上げます。

    cpは、テンプレートに一致する最初のファイルを現在のディレクトリにコピーし、1つのインスタンスの実行内で同じ宛先に上書きできないため、エラーで他のファイルを誓います。

    念のため、以下を実行するとどうなりますか。

     cp /home/*/.ssh/authorized_keys /home/*/ssh/authorized_new
          
          





    答え
    魔法ではなく、単なる構文エラー;)




    3.しかし、これは本当に楽しいパズルです!



    私はそれを最初に投げたいとさえ思ったが、軽食のためにそれを残すことにした。 したがって、状況は次のとおりです。

     #   : $ touch file{1..9} $ ls -1 file1 file2 file3 file4 file5 file6 file7 file8 file9
          
          





    ls -1 」でそれらを印刷し、簡単なレギュラーで最初の5つを除外します:

     $ ls -1 | grep file[1-5]
          
          





    結果は空ですか? 一体何? 私のファイルはどこですか?

    適切なチーム
    すべてが非常に簡単です。 正しいでしょう:

     $ ls -1 | grep "file[1-5]" file1 file2 file3 file4 file5
          
          



    しかし、なぜですか?
    ファイルマスク(ワイルドカード)で次の文字が使用されていることは誰もが知っています および

    また、ワイルドカードに適合するファイルエンティティが存在する場合、後者はシェルによってスペースで区切られた値のリストに展開され、その後のみ引数のリストが変更された状態でコマンドが実行されます。 適切なファイルエンティティがない場合、パターンは変更されません。

    簡単な視覚的な例
     $ mkdir test $ cd test $ echo file* file* $ touch file1 $ echo file* file1 $ touch file2 $ echo file* file1 file2
          
          



    つまり、ワイルドカードを使用すると、機能するコマンド、場合によっては機能しないコマンド、場合によっては方法がわからないコマンドを取得できます。 これは、ワイルドカードを引用符で囲むだけで修正されます。



    連続してすべてを引用する必要はありません。そのため、ワイルドカードではない単純な単語や正規表現が引用符なしで使用されることがよくあります。



    上記はよく知られていますが、* nixが[abc]形式の文字のワイルドカード列挙をサポートしていることを誰もが知っているわけではありません。



    このケースでは、シェルはマスクを「開き」、長い文字列をgrepに渡して、コマンド「ls -1 | grep file1 file2 file3 file4 file5」。 この場合、grepはファイルfile2、file3、file4、file5で文字列file1を探しますが、ファイルが空なので何も返しません(明確にするためにmickvavに感謝します)。



    適切なファイルがないディレクトリでワイルドカードを含むコマンドを実行した場合、コマンドは変更されず、前の例のように「 * 」が表示されます。

     $ cd ..;echo file[1-5] file[1-5]
          
          





    ちなみに、多くの初心者は、よく知られている古いマスクを使用していても、たとえばfindコマンドを実行したときに次のようなエラーを犯します

     $ find . -name file* find: paths must precede expression: file2
          
          







    結論:引用符を使用してください!



    ワイルドカードの文字列挙は、 範囲反転の両方をサポートします 。 例:

     #  ,   1-5 $ echo file[1-5] file1 file2 file3 file4 file5 #  ,    1-5: $ echo file[^1-5] file6 file7 file8 file9
          
          







    4.ファイル拡張子をカットする簡単な方法は何ですか?



    答え
    標準的で一般的な方法は、 basenameユーティリティを使用することです。これは、左端までカットします。追加のパラメータを指定すると、右側のサフィックスも追加でカットされます。 たとえば、 file.txtと接尾辞.txtを記述します

     $ basename file.txt .txt file
          
          





    ただし、このような単純なアクションのために個別のプロセス全体を開始することはできず、 bashの内部変換(bash変数の展開)を使用することはできません。

     $ filename=file.txt; echo ${filename%.*} file
          
          





    またはその逆に、ファイル名を切り取り、拡張子のみを残します。

     filename=file.txt; echo ${filename##*.} txt
          
          





    どのように機能しますか?
    -最後から最初の適切なパターンまでのすべての文字を切り取ります(検索は右から左に進みます)

    %% -最後から最後に一致するパターンまでのすべての文字を切り取ります(右から左へ)

    -最初から適切な最初のパターンにカットします(検索は左から右に進みます)

    ## -最初から最後の適切なパターンへのカット(左から右へ)



    したがって、「 $ {filename%。*} 」の意味-右から左に向かって、すべての文字(*)を通過して最初のポイントに到達します。 見つかったものを切り取ります。

    「$ {filename %%。*)」を使用した場合、ポイントが複数回出現するファイルでは、最後のポイントに到達して余分な部分を切り捨てます。

     $ filename="file.hello.txt"; echo "${filename%%.*}" file
          
          







    5.リダイレクト<<<および<<<



    最初に、名前付きストリームまたはファイルから「<」をリダイレクトします。 それは長い間、過酷な管理者のとうもろこしで知られ、擦り切れてきました。 したがって、私たちはすぐに他の2つに目を向けます。



    << 、いわゆるヒアドキュメントの構築。 スクリプトに複数行のテキストを直接配置し、外部ストリームからのようにリダイレクトすることができます。

     $ cat <<EOF \ hello, \ Habr \ EOF hello, Habr
          
          





    はファイルからデータを読み取ります。 STDINファイルにリダイレクトします- ヒアドキュメントの構成はその場ですぐに生成するため、別のファイルを作成する必要はありません。


    これは、外部ユーティリティを呼び出して大量のデータを供給するための非常に便利な方法です。 しかし、最近では、 <<<

    そして、ここに理由があります
    第一に、 <<<の方が読みやすく、第二に、複数行のデータも<<<経由で送信できます。 3番目-... 3番目にはもうありませんが、最初の2つで十分でした。 2つの読みやすさの例を比較します。

     #!/bin/bash . load_credentials sqlplus -s $connstring << EOF set line 1000 select name, lastlogin from users; exit; EOF
          
          





     #!/bin/bash . load_credentials SLQ_REQUEST=" set line 1000 select name, lastlogin from users; exit;" sqlplus -s ${connstring} <<<"${SQL_REQUEST}"
          
          







    私の意見では、2番目のオプションは潜在的にもっと便利に見えます。 都合の良い場所に複数行の変数を設定し、 <<<で使用できます。

    そして、短いリクエストで、一般的にすべてがうまく見えます:

     #!/bin/bash . load_credentials sqlplus -s ${connstring} <<<"select name, lastlogin from users;exit;"
          
          







    より大きなスクリプトで、より本格的なクエリを使用して操作し、変数からのリダイレクトで<<<を使用する場合(そして、特別に指定された場所でコメントを備えた変数自体を事前に宣言できます)、コードははるかに読みやすくなります。

    複数行のデータのヒープをリダイレクトして複数の外部コマンドを呼び出し、これらのコマンドを異なるネストの複数のif / loop構造内に配置する必要があることを想像してください。

    ここでのドキュメントは、そのようなコードのフォーマットと読みやすさをひどく損ないます。





    6.フォルダーへのハードリンクを作成することはできますか?



    詳細な回答
    もちろんできます! しかし、全員ではありません。 POSIXファイルシステムはハードリンクを積極的に使用しており、ハードリンクは常に表示されています! 例:

     #   test $ mkdir test #        iNode  test $ stat -c "LinkCount:%h iNode:%i" test LinkCount:2 iNode:522366
          
          





    どうやって? 作成したばかりで既に2つのリンクがありますか?

     #     test $ cd test #       "." $ stat -c "LinkCount:%h iNode:%i" . LinkCount:2 iNode:522366
          
          





    どちらの場合も、同じiNode番号が表示されます。 つまり、 テストとその中の「 」は同じディレクトリです。 そして、「 」は特別なエイリアスbashではなく、オペレーティングシステムでもありません。 これは、ファイルシステムレベルの単なるハードリンクです。 もう少し確認しましょう。

     #   test2   test $ mkdir test2 #    test2 $ cd test2 #      ".." $ stat -c "LinkCount:%h iNode:%i" .. LinkCount:3 iNode:522366
          
          





    .. 」には、テストディレクトリに対応する同じiNode 522366があります 。 そして、リンク数が増加しました。



    結論:フォルダーへのハードリンクは、ディレクトリシステムの構築に使用されるファイルシステムの必須部分です。 ただし、ディレクトリへの任意のハードリンクの作成をユーザーに許可すると、ユーザーは間違いを犯してループリンクを作成する可能性があります。



    この場合、ディレクトリツリーを介して実行されるすべてのコマンド(find、du、ls)は無限ループに入り、割り込みまたはスタックオーバーフローによってのみ終了するため、ユーザーコマンドはありません。




    これで私はすべてを持っています。

    私はこの機会に、調査で注目される人々に前もって感謝を伝えます!



    更新 :フォーマットが少し修正され、不正確なを修正してくれたmickvavに感謝します。



All Articles