26

複数のディレクトリ( fgit )で同じコマンドを実行するシェルスクリプトがあります。ディレクトリごとに、現在のプロンプトとそこで実行されるコマンドを表示したいと思います。デコードされた(展開された)文字列に対応する文字列を取得するにはどうすればよいPS1ですか?たとえば、私のデフォルトのPS1は

${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$(__git_ps1 ' (%s)')$

username@hostname:/path$そして、できれば(必ずしもそうとは限りませんが)素敵な色で、結果のプロンプトをエコーし​​たいと思います。Bashのマニュアルをざっと見てみると、明確な答えは明らかにならずecho -e $PS1、色を評価するだけです。

4

7 に答える 7

23

@PBash 4.4以降、拡張機能を使用できます。

まず、ヒアドキュメントmypromptを使用してプロンプト文字列を変数に入れます。read -r

read -r myprompt <<'EOF'
${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$(__git_ps1 ' (%s)')$ 
EOF

プロンプトを出力するには(そうである場合に解釈されるためPS1)、展開を使用します${myprompt@P}

$ printf '%s\n' "${myprompt@P}"
gniourf@rainbow:~$
$

(実際には、ここには表示されない文字がいくつかあります\001が、この投稿を編集しようとすると表示されます。コマンドを入力すると、ターミナルにも表示されます)。\002\[\]


これらを取り除くために、bashメーリングリストでDennis Williamsonによって送信されたトリックは、read -e -pこれらの文字がreadlineライブラリによって解釈されるように使用することです。

read -e -p "${myprompt@P}"

mypromptこれにより、正しく解釈された状態でユーザーにプロンプ​​トが表示されます。

この投稿に対して、Greg Wooledgeは、文字列からとを削除した方がよいと答えまし\001\002。これは次のように実現できます。

myprompt=${myprompt@P}
printf '%s\n' "${myprompt//[$'\001'$'\002']}"

この投稿に対して、Chet Rameyは、でライン編集を完全にオフにすることもできると答えましたset +o emacs +o vi。したがって、これも同様です。

( set +o emacs +o vi; printf '%s\n' "${myprompt@P}" )
于 2016-05-10T12:02:41.400 に答える
12

オープンソースソフトウェアの大きな利点の1つは、ソースがオープンであることです:-)

Bash自体はこの機能を提供しませんが、サブセットを提供するために使用できるさまざまなトリックがあります(置換など\u$USERbashただし、これには多くの機能の複製が必要であり、コードが将来何をするにしても同期が保たれるようにする必要があります。

プロンプト変数のすべての機能を利用したい場合(そして、少しのコーディングで手を汚してもかまわない場合(そして、気になっている場合は、なぜここにいるのですか?))、に追加するのは簡単です。シェル自体。

bash(バージョン4.2を見ている)のコードをダウンロードすると、次の関数y.tab.cを含むファイルがあります。decode_prompt_string()

char *decode_prompt_string (string) char *string; { ... }

これは、PSxプロンプトの変数を評価する関数です。この機能を(シェルで使用されるだけでなく)シェル自体のユーザーに提供できるようにするには、次の手順に従って内部コマンドを追加しますevalps1

まず、support/mkversion.sh「実際の」と混同しないようにbash、またFSFが保証目的ですべての知識を拒否できるように変更します:-) 1行変更するだけです(-paxビットを追加しました)。

echo "#define DISTVERSION \"${float_dist}-pax\""

次に、変更builtins/Makefile.inして新しいソースファイルを追加します。これにはいくつかの手順が必要です。

$(srcdir)/evalps1.def(a)の末尾に追加しDEFSRCます。

(b)evalps1.oの末尾に追加しOFILESます。

(c)必要な依存関係を追加します。

evalps1.o: evalps1.def $(topdir)/bashtypes.h $(topdir)/config.h \
           $(topdir)/bashintl.h $(topdir)/shell.h common.h

第三に、ファイル自体を追加します。これは、コマンドbuiltins/evalps1.defを実行したときに実行されるコードです。evalps1

This file is evalps1.def, from which is created evalps1.c.
It implements the builtin "evalps1" in Bash.

Copyright (C) 1987-2009 Free Software Foundation, Inc.

This file is part of GNU Bash, the Bourne Again SHell.

Bash is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Bash is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Bash.  If not, see <http://www.gnu.org/licenses/>.

$PRODUCES evalps1.c

$BUILTIN evalps1
$FUNCTION evalps1_builtin
$SHORT_DOC evalps1
Outputs the fully interpreted PS1 prompt.

Outputs the PS1 prompt, fully evaluated, for whatever nefarious purposes
you require.
$END

#include <config.h>
#include "../bashtypes.h"
#include <stdio.h>
#include "../bashintl.h"
#include "../shell.h"
#include "common.h"

int
evalps1_builtin (list)
     WORD_LIST *list;
{
  char *ps1 = get_string_value ("PS1");
  if (ps1 != 0)
  {
    ps1 = decode_prompt_string (ps1);
    if (ps1 != 0)
    {
      printf ("%s", ps1);
    }
  }
  return 0;
}

その大部分はGPLライセンス(私がそれを変更したのでexit.def)であり、最後に取得してデコードするための非常に単純な関数がありPS1ます。

最後に、トップレベルのディレクトリにあるものをビルドします。

./configure
make

bash表示される実行可能ファイルの名前をに変更できますがpaxsh、祖先と同じくらい普及することはないと思います:-)

そしてそれを実行すると、実際に動作していることがわかります。

pax> mv bash paxsh

pax> ./paxsh --version
GNU bash, version 4.2-pax.0(1)-release (i686-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pax> ./paxsh

pax> echo $BASH_VERSION
4.2-pax.0(1)-release

pax> echo "[$PS1]"
[pax> ]

pax> echo "[$(evalps1)]"
[pax> ]

pax> PS1="\h: "

paxbox01: echo "[$PS1]"
[\h: ]

paxbox01: echo "[$(evalps1)]"
[paxbox01: ]

変数の1つをプロンプトに入れると、コマンドがそれを評価して結果を出力している間PSx、エコー$PS1は単に変数を提供します。evalps1

確かに、内部コマンドを追加するためにコードを変更するbashことはやり過ぎだと考える人もいるかもしれませんが、の完全な評価が必要な場合はPS1、それは確かにオプションです。

于 2013-01-09T09:29:17.247 に答える
9

$PS1エスケープ置換を自分で処理してみませんか?次のような一連の置換:

p="${PS1//\\u/$USER}"; p="${p//\\h/$HOSTNAME}"

ちなみに、zshにはプロンプトエスケープを解釈する機能があります。

print -P '%n@%m %d'

また

p=${(%%)PS1}
于 2010-08-10T20:37:41.473 に答える
5

私はBashを修正して改善するというアイデアが好きで、Bashにパッチを適用する方法についてのpaxdiabloの詳細な回答に感謝しています。いつか行きます。

ただし、Bashのソースコードにパッチを適用しないと、回避策はBashとその組み込み機能のみを使用するため、移植性があり、機能が重複しないワンライナーハックがあります。

x="$(PS1=\"$PS1\" echo -n | bash --norc -i 2>&1)"; echo "'${x%exit}'"

tty'sで何か奇妙なことが起こっていることに注意してくださいstdio。これも機能するので、次のように表示されます。

x="$(PS1=\"$PS1\" echo -n | bash --norc -i 2>&1 > /dev/null)"; echo "'${x%exit}'"

したがって、ここで何が起こっているのかはわかりませんがstdio、私のハックはBash 4.2、NixOS GNU/Linuxで機能しています。Bashソースコードにパッチを適用することは間違いなくより洗練されたソリューションであり、Nixを使用しているので、これを行うのは非常に簡単で安全なはずです。

于 2014-06-03T03:55:54.467 に答える
3

2つの答え:「Purebash」と「bash+sed」

イントロ

もちろん、のバージョン4.4から、gniourf_gniourfが正しく応答したため、パラメーター変換を使用する必要があります。

ExpPS1=${PS1@P}
echo ${ExpPS1@Q}
$'\001\E]0;user@host: ~\a\002user@host:~$ '

見るman -Pless\ +/parameter\\\ transformation bash

しかし、古いbashの場合、または文字列変数で遊ぶ場合でも...

bash+を使用したプロンプト拡張sed

私のハックがあります:

ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1 |
              sed ':a;$!{N;ba};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"

説明:

ランニングbash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1

次のようなものが返される場合があります。

user@host:~$ 
user@host:~$ exit

その後、sedコマンドは

  • すべての行を1つのバッファー(:a;$!{N;ba};)に入れてから
  • <everything, terminated by end-of-line><prompt>end-of-line<prompt>exitに置き換え<prompt>ます。(s/^\(.*\n\)*\(.*\)\n\2exit$/\2/)。
    • どこに<everything, terminated by end-of-line>なる\1
    • <prompt>なり\2ます。

テストケース:

while ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1 |
          sed ':a;$!{N;ba};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"
    read -rp "$ExpPS1" && [ "$REPLY" != exit ] ;do
    eval "$REPLY"
  done

そこから、あなたは一種の疑似インタラクティブシェルにいます(readline機能はありませんが、それは問題ではありません)...

ubuntu@ubuntu:~$ cd /tmp
ubuntu@ubuntu:/tmp$ PS1="${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$ "
ubuntu@ubuntu:/tmp$ 

(最後の行ubuntuは緑、、、黒の両方で@印刷:され$ 、パス(/tmp)は青で印刷されます)

ubuntu@ubuntu:/tmp$ exit
ubuntu@ubuntu:/tmp$ od -A n -t c <<< $ExpPS1 
 033   [   1   ;   3   2   m   u   b   u   n   t   u 033   [   0
   m   @ 033   [   1   ;   3   2   m   u   b   u   n   t   u 033
   [   0   m   : 033   [   1   ;   3   4   m   ~ 033   [   0   m
   $  \n

純粋な

シンプルで迅速:

ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1)"
mapfile ExpPS1 <<<"${ExpPS1%exit}"
ExpPS1=( "${ExpPS1[*]::${#ExpPS1[@]}/2}" )

それから今

declare -p ExpPS1
declare -a ExpPS1=([0]=$'\E]0;ubuntu@ubuntu: ~\aubuntu@ubuntu:~$ \n')

また

echo ${ExpPS1@Q}
$'\E]0;ubuntu@ubuntu: ~\aubuntu@ubuntu:~$ \n'

複数行のプロンプトを使用したクイックテスト:

ExpPS1="$(bash --rcfile <(echo "PS1='Test string\n$(date)\n$PS1'"
    ) -i <<<'' 2>&1)";
mapfile ExpPS1 <<<"${ExpPS1%exit}"
ExpPS1=( "${ExpPS1[*]::${#ExpPS1[@]}/2}" )    

echo ${ExpPS1@Q}
$'Test string\r\n Sat Jan 9 19:23:47 CET 2021\r\n \E]0;ubuntu@ubuntu: ~\aubuntu@ubuntu:~$ \n'

または

od -A n -t c  <<<${ExpPS1}
   T   e   s   t       s   t   r   i   n   g  \r  \n       S   a
   t       J   a   n           9       1   9   :   2   6   :   3
   9       C   E   T       2   0   2   1  \r  \n     033   ]   0
   ;   u   b   u   n   t   u   @   u   b   u   n   t   u   :    
   ~  \a   u   b   u   n   t   u   @   u   b   u   n   t   u   :
   ~   $      \n  \n

文字列が正しいことを確認するために、少しテストを追加できることに注意してください。

ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1)"
mapfile ExpPS1 <<<"${ExpPS1%exit}"
[ "${ExpPS1[*]::${#ExpPS1[@]}/2}" = "${ExpPS1[*]: -${#ExpPS1[@]}/2}" ] ||
    echo WARNING: First half seem not match last half string.
ExpPS1=( "${ExpPS1[*]::${#ExpPS1[@]}/2}" )
于 2016-05-10T09:11:07.973 に答える
1

もう1つの可能性:bashソースコードを編集せずに、scriptユーティリティ(bsdutilsubuntuのパッケージの一部)を使用する:

$ TEST_PS1="\e[31;1m\u@\h:\n\e[0;1m\$ \e[0m"
$ RANDOM_STRING=some_random_string_here_that_is_not_part_of_PS1
$ script /dev/null <<-EOF | awk 'NR==2' RS=$RANDOM_STRING
PS1="$TEST_PS1"; HISTFILE=/dev/null
echo -n $RANDOM_STRING
echo -n $RANDOM_STRING
exit
EOF
<prints the prompt properly here>

scriptコマンドは指定されたファイルを生成し、出力はstdoutにも表示されます。filenameを省略すると、typescriptというファイルが生成されます。

この場合、ログファイルには関心がないため、filenameは。として指定され/dev/nullます。代わりに、スクリプトコマンドのstdoutがawkに渡されてさらに処理されます。

  1. コード全体を関数にカプセル化することもできます。
  2. また、出力プロンプトを変数に割り当てることもできます。
  3. PROMPT_COMMANDこのアプローチは、 ...の解析もサポートします。
于 2014-03-11T11:36:34.250 に答える
0
  1. ps=${ps@P}(bash 4.4)で展開します
  2. \x01との間を削除します(とプレースホルダー\x02をbashで置き換えて作成します。\[\]
  3. 残っているすべての文字を確認してください
ps1_size(){
  # Ref1: https://stackoverflow.com/questions/3451993/how-to-expand-ps1
  >&2 echo -e "\nP0: Raw"
  local ps=$PS1
  echo -n "$ps" | xxd >&2 

  >&2 echo -e "\nP1: Expanding (require bash 4.4)"
  ps=${ps@P}
  echo -n "$ps" | xxd >&2 

  >&2 echo -e "\nP2: Removing everything 01 and 02"
  shopt -s extglob
  ps=${ps//$'\x01'*([^$'\x02'])$'\x02'}
  echo -n "$ps" | xxd >&2 

  >&2 echo -e "\nP3: Checking"
  if [[ "$ps" =~ [\x07\x1b\x9c] ]]; then
    # Check if escape inside
    # 07 => BEL
    # 1b => ESC
    # 9C => ST
    >&2 echo 'Warning: There is an escape code in your PS1 which is not betwwen \[ \]'
    >&2 echo "Tip: put \[ \] around your escape codes (ctlseqs + associated parameters)"
    echo -n "$ps" | xxd >&2
  # Check printable characters <= 20 .. 7e, and newline
  # -- Remove the trailing 0x0a (BEL)
  elif [[ "$ps" =~ [^[:graph:][:space:]] ]]; then
    >&2 echo 'Warning: There is a non printable character in PS1 which is not between \[ \]'
    >&2 echo "Tip: put \[ \] around your escape codes (ctlseqs + associated parameters)"
    echo "$ps"
    echo -n "$ps" | xxd >&2 
  fi

  # Echo result
  echo -n "${#ps}"
}

ps1_size

次のような出力が必要です。

~/Software/Bash/Mouse (master)$ source ../ps1_size.sh

P0: Raw
00000000: 5c5b 5c65 5d30 3b60 7061 7273 655f 7469  \[\e]0;`parse_ti
00000010: 746c 6560 5c30 3037 5c5d 5c5b 5c65 5b33  tle`\007\]\[\e[3
00000020: 326d 5c5d 5c77 205c 5b5c 655b 3333 6d5c  2m\]\w \[\e[33m\
00000030: 5d60 7061 7273 655f 6769 745f 6272 616e  ]`parse_git_bran
00000040: 6368 605c 5b5c 655b 306d 5c5d 2420       ch`\[\e[0m\]$

P1: Expanding (require bash 4.4)
00000000: 011b 5d30 3b7e 2f53 6f66 7477 6172 652f  ..]0;~/Software/
00000010: 4261 7368 2f4d 6f75 7365 0702 011b 5b33  Bash/Mouse....[3
00000020: 326d 027e 2f53 6f66 7477 6172 652f 4261  2m.~/Software/Ba
00000030: 7368 2f4d 6f75 7365 2001 1b5b 3333 6d02  sh/Mouse ..[33m.
00000040: 286d 6173 7465 7229 011b 5b30 6d02 2420  (master)..[0m.$

P2: Removing everything 01 and 02
00000000: 7e2f 536f 6674 7761 7265 2f42 6173 682f  ~/Software/Bash/
00000010: 4d6f 7573 6520 286d 6173 7465 7229 2420  Mouse (master)$

P3: Checking
32~/Software/Bash/Mouse (master)$

一部の制御文字が存在する場合は、このスタックオーバーフローで説明されているように削除できます:テキストストリームからのANSIカラーコードの削除。次を使用して、githubのSCIとOSCを削除しました:mouse_xterm

  # Sanitize, in case
  ps=$(LC_ALL=C sed '
    # Safety
    s/\x01\|\x02//g;
    # Safety Remove OSC https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
    # 20 .. 7e => printable characters
    # 07 => BEL
    # 9C => ST
    # 1b 5C => ESC + BS
    s/\x1b\][0-9;]*[\x20-\x7e]*\([\x07\x9C]\|\x1b\\\)//g;
    # Safety: Remove all escape sequences https://superuser.com/questions/380772/removing-ansi-color-codes-from-text-stream
    s/\x1b\[[0-9;]*[a-zA-Z]//g;
  ' <<< "$ps")
于 2021-12-29T20:26:44.010 に答える