「source file.sh」、「./ file.sh」、「sh file.sh」、「。」を使用してシェルスクリプトを実行する場合の違いは何ですか。./file.sh”?


13

コードを見てください:

#!/bin/bash
read -p "Eneter 1 for UID and 2 for LOGNAME" choice
if [ $choice -eq 1 ]
then
        read -p "Enter UID:  " uid
        logname=`cat /etc/passwd | grep $uid | cut -f1 -d:`
else
        read -p "Enter Logname:  " logname
fi
not=`ps -au$logname | grep -c bash`
echo  "The number of terminals opened by $logname are $not"

このコードは、同じPCでユーザーが開いた端末の数を調べるために使用されます。現在、xとyの2人のユーザーがログオンしています。現在、yとしてログインしており、ユーザーxに3つの端末が開いています。上記のようにさまざまな方法でこのコードをyで実行すると、結果は次のようになります。

$ ./file.sh
The number of terminals opened by x are 3

$ bash file.sh
The number of terminals opened by x are 5

$ sh file.sh
The number of terminals opened by x are 3

$ source file.sh
The number of terminals opened by x are 4

$ . ./file.sh
The number of terminals opened by x are 4

注:これらすべての実行可能ファイルに1とuid 1000を渡しました。

では、これらすべての違いを説明していただけますか?


違いは、実行されるシェルです。shはbashではない
-j0h

2
同じコンテキストで実行しているため、最後の2つの実行も異なります。詳細はこちら
ザカエラ

私は、数のbashのインスタンス(。ここではそれが無いに等しい端末の)他のユーザー(私たちは、ログインしていないのと同じユーザー)で開かれ、別の番号がそれぞれの場合に来た理由を説明できるカウントしようとしています
ラマナ・レディ

@RamanaReddy他のユーザーはスクリプトを実行したか、新しいタブを開始した可能性があります。知るか?
ムル

回答:


21

唯一の大きな違いは、スクリプトのソースと実行の違いです。source foo.shそれをソースし、あなたが示す他のすべての例が実行されます。さらに詳細に:

  1. ./file.sh

    これfile.shにより、現在のディレクトリにあるというスクリプトが実行されます(./)。通常、実行するcommandと、シェルは$PATHと呼ばれる実行可能ファイルを探してディレクトリを検索しますcommand/usr/bin/commandまたはなどのフルパスを指定すると./command$PATHは無視され、その特定のファイルが実行されます。

  2. ../file.sh

    これは./file.sh、の現在のディレクトリを検索する代わりfile.shに、親ディレクトリ(../)を検索することを除いて、基本的に同じです。

  3. sh file.sh

    これsh ./file.shは、上記と同様file.sh、現在のディレクトリで呼び出されるスクリプトを実行します。違いは、shシェルで明示的に実行していることです。Ubuntuシステムではdash、そうではありませんbash。通常、スクリプトには、実行するプログラムを提供するシェバン行があります。別のものでそれらを呼び出すことはそれをオーバーライドします。例えば:

    $ cat foo.sh
    #!/bin/bash  
    ## The above is the shebang line, it points to bash
    ps h -p $$ -o args='' | cut -f1 -d' '  ## This will print the name of the shell

    このスクリプトは、実行に使用されたシェルの名前を単に出力します。さまざまな方法で呼び出されたときに返されるものを見てみましょう:

    $ bash foo.sh
    bash
    $ sh foo.sh 
    sh
    $ zsh foo.sh
    zsh

    そのため、スクリプトを呼び出して呼び出すshell scriptと、shebang行(存在する場合)がオーバーライドされ、指定したシェルでスクリプトが実行されます。

  4. source file.sh または . file.sh

    これは、驚くべきことに、スクリプトのソースと呼ばれます。キーワードsourceは、シェルの組み込み.コマンドのエイリアスです。これは、現在のシェル内でスクリプトを実行する方法です。通常、スクリプトが実行されると、現在のシェルとは異なる独自のシェルで実行されます。説明する:

    $ cat foo.sh
    #!/bin/bash
    foo="Script"
    echo "Foo (script) is $foo"

    ここfooで、親シェルで変数を別の値にfoo設定してからスクリプトを実行すると、スクリプトは異なる値を出力します(スクリプト内でも設定されているため)がfoo、親シェルの値は変更されません:

    $ foo="Parent"
    $ bash foo.sh 
    Foo (script) is Script  ## This is the value from the script's shell
    $ echo "$foo"          
    Parent                  ## The value in the parent shell is unchanged

    ただし、スクリプトを実行する代わりにソースする場合、同じシェルで実行されるためfoo、親の値が変更されます。

    $ source ./foo.sh 
    Foo (script) is Script   ## The script's foo
    $ echo "$foo" 
    Script                   ## Because the script was sourced, 
                             ## the value in the parent shell has changed

    したがって、スクリプトは、実行元のシェルに影響を与えるいくつかのケースで使用されます。通常、シェル変数を定義し、スクリプトの終了後に使用可能にするために使用されます。


これらすべてを念頭に置いて、さまざまな答えを得る理由は、まず、スクリプトが思ったとおりに動作しないからです。bashの出力に現れる回数をカウントしpsます。これは、開いている端末の数ではなく、実行中のシェルの数です(実際、それでもありませんが、それは別の議論です)。明確にするために、スクリプトをこれに少し簡略化しました。

#!/bin/bash
logname=terdon
not=`ps -au$logname | grep -c bash`
echo  "The number of shells opened by $logname is $not"

そして、単一のターミナルのみを開いて、さまざまな方法で実行します。

  1. 直接起動、./foo.sh

    $ ./foo.sh
    The number of shells opened by terdon is 1

    ここでは、シバンラインを使用しています。これは、そこに設定されているものによってスクリプトが直接実行されることを意味します。これは、スクリプトがの出力に表示される方法に影響しpsます。としてリストされるのbash foo.shではなく、として表示されるだけなのでfoo.shgrep見逃すことになります。実際には、3つのbashインスタンスが実行されています。親プロセス、スクリプトps実行するbash、およびコマンドを実行する別のインスタンスです。これは重要です。コマンド置換(`command`または$(command))を使用してコマンドを起動すると、親シェルのコピーが起動され、コマンドが実行されます。ただし、ここでは、ps出力を表示する方法のため、これらのいずれも表示されません。

  2. 明示的な(bash)シェルを使用した直接起動

    $ bash foo.sh 
    The number of shells opened by terdon is 3

    ここでは、で実行しているためbash foo.sh、の出力psが表示されbash foo.sh、カウントされます。したがって、ここには親プロセス、bashスクリプトの実行、およびクローンシェル(実行ps)がすべて表示されpsますbash。これは、コマンドに単語が含まれるため、それぞれが表示されるためです。

  3. 別のシェルでの直接起動(sh

    $ sh foo.sh
    The number of shells opened by terdon is 1

    これは、を使用してスクリプトを実行しているため、ではshありませんbash。したがって、唯一のbashインスタンスは、スクリプトを起動した親シェルです。上記の他のすべてのシェルは、sh代わりに実行されています。

  4. ソーシング(.またはsource、同じものによる)

    $ . ./foo.sh 
    The number of shells opened by terdon is 2

    上で説明したように、スクリプトをソースすると、親プロセスと同じシェルで実行されます。ただし、psコマンドを起動するために別のサブシェルが開始され、合計で2つになります。


最後に、実行中のプロセスをカウントする正しい方法は、解析するのではpsなくを使用することpgrepです。これらの問題はすべて、実行するだけで回避できます。

pgrep -cu terdon bash

したがって、常に正しい番号を出力するスクリプトの作業バージョンは次のとおりです(コマンドの置換がないことに注意してください)。

#!/usr/bin/env bash
user="terdon"

printf "Open shells:"
pgrep -cu "$user" bash

他のすべての起動方法では、ソースを取得すると1が返され、スクリプトを実行するために新しいbashが起動されるため2が返されます。sh子プロセスはでないため、起動時に1を返しbashます。


コマンド置換が親シェルのコピーを起動すると言うとき、このコピーは、。/ foo.shでスクリプトを実行するときのようなサブシェルとどのように異なりますか?
ディディエA.

コマンド置換なしでpgrepを実行すると、スクリプトが実行されるのと同じシェル内から実行されていると思いますか?ソーシングに似ていますか?
ディディエA.

@didibusどういう意味かわかりません。コマンド置換はサブシェルで実行されます。./foo.sh親のコピーではない新しいシェルで実行されます。たとえばfoo="bar"、端末で設定してから実行するスクリプトを実行するecho $fooと、スクリプトのシェルは変数の値を継承しないため、空の行が表示されます。pgrepは別のバイナリであり、はい、実行中のスクリプトによって実行されます。
テルドン

基本的に、「コマンドの置換がないことに注意してください」に関する説明が必要です。スクリプトからpgrepバイナリを実行しても余分なシェルが追加されないのに、コマンド置換を使用してpsバイナリを実行するとなぜですか?第二に、「親シェルのコピー」に関する説明が必要です。これは、親のシェル変数が子にコピーされるサブシェルのようなものですか?なぜコマンド置換がそれを行うのですか?
ディディエA.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.