2

これには、bash と tcsh の両方の知識が必要です。tcsh インタープリターを使用して、いくつかの引数を指定してコマンドを呼び出す bash スクリプトに行を追加したいと思います。呼び出したいコマンドがls(ばかげていますが、問題を示しています) であるとします。含まれている場合my_script

#!/bin/bash
/bin/tcsh -c "ls"' "$*"' "${@}"

そして、次のように呼び出します。

my_script "first file" "second file"

私はこれを得る:

ls: first file second file: No such file or directory

問題は、tcsh が 2 つの引数でfirst file second fileはなく 1 つの引数のみを受け取ることです。どうすればこれを修正できますか?

4

3 に答える 3

3

コメントにもかかわらず、直接的な問題はありtcshません (信じてください、私は C シェルのファンではありません)、bashそれ自体にも問題はありません。tcsh実際、をに置き換えた場合、問題は同様になりますbash

問題は、あなたがやろうとしていることは実際には非常に難しいということです. 説明させてください...

スクリプトでは、引数にスペースを保持するなど、正しく解釈されるbash有効なコマンド ラインを含む単一の文字列を作成しようとしています。tcsh

段階的に答えを導き出す

いくつかの簡単なものから始めましょう — スペースのない引数:

set -- /bin/ls /bin/sh /bin/bash   # Set the arguments to bash
/bin/tcsh -c "ls -l $*"

これで問題なく動作します。これは C シェルを実行し、C シェルは文字列を処理して実行します。

ls -l /bin/ls /bin/sh /bin/bash

したがって、問題は、コマンド全体が単一の文字列として指定されている場合に、スペースを含む引数を C シェルに確実に中継する方法です。

これで問題が発生することはすでにわかっています。

mkdir "./a b c" "./d e f"
set -- "a b c" "d e f"          # Two arguments with spaces
/bin/tcsh -c "ls -al $*"

私のマシンでは、次のようになります。

ls: a: No such file or directory
ls: b: No such file or directory
ls: c: No such file or directory
ls: d: No such file or directory
ls: e: No such file or directory
ls: f: No such file or directory

手動で拡張を行うと、(この限られた例では) 次のようにして目的の結果を得ることができます。

mkdir "./a b c" "./d e f"
set -- "a b c" "d e f"          # Two arguments with spaces
/bin/tcsh -c "ls -al 'a b c' 'd e f'"

これにより、次の結果が得られます。

a b c:
total 0
drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:21 .
drwxr-xr-x  4 jleffler  staff  136 Aug 25 12:21 ..

d e f:
total 0
drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:21 .
drwxr-xr-x  4 jleffler  staff  136 Aug 25 12:21 ..

a b c(ここから先は、' ' と ' ' の2 つのディレクトリd e fが毎回作成されずに存在すると仮定します。)

したがって、目的は、C シェルによって自動的に解釈されるときに安全な文字列を作成する方法を見つけることでなければなりません (示されているように手動ではありません)。C シェルが持っているメタシンタックス ズー (多くの特殊文字) のため、すべてのタスクを実行するのは困難ですが、最初に簡単なこと (スペースとメタ文字なし) を実行しましょう。

各引数について、最初と最後に一重引用符を追加し、文字列内の一重引用符が保護されるようにします。それはそれ自身の小さなパーティーです。秘訣は、埋め込まれた単一引用符を'\''、最初の単一引用符が現在の単一引用符で囲まれた文字列を終了し、バックスラッシュの単一引用符が単一引用符を埋め込み、最後の単一引用符が新しい単一引用符で囲まれた文字列を開始するシーケンスに置き換えることです。これを現在のコマンド文字列の最後に追加します。したがって、これは次のことにつながります。

set -- "a b c" "d e f"          # Two arguments with spaces
cmd="ls -al"
for arg in "$@"
do escaped=$(sed -e "s/'/'\\''/g" -e "s/^/'/" -e "s/$/'/" <<< "$arg")
   cmd="$cmd $escaped"
done
echo "$cmd"
tcsh -c "$cmd"

これにより、次の結果が得られます(もちろん、ls行は からのものechoです):

ls -al 'a b c' 'd e f'
a b c:
total 0
drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:21 .
drwxr-xr-x  4 jleffler  staff  136 Aug 25 12:21 ..

d e f:
total 0
drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:21 .
drwxr-xr-x  4 jleffler  staff  136 Aug 25 12:21 ..

OK、これまでのところ、とても良いです。メタシンタクティック動物園はどうですか?幸いなことに、ほとんどの文字は一重引用符内で特別な意味を持ちません。

より複雑なディレクトリをリストに追加する時間です (これらは、質問の間も存続します)。どのような名前が作成されているかを確認してください。シェルの引用をかなりよく理解する必要があります。

演習として、この質問で作成した各ディレクトリ名について、単一引用符で囲んだ場合、二重引用符で囲んだ場合、および引数全体を引用符なしで囲んだ場合に同じ結果が得られる代替を記述します。

$ mkdir '! % *' '$(pwd)' '`pwd`'

また、スクリプトはほとんど変更されていません。シェル グロブを使用してディレクトリ名のリストを生成し、各引数を順番にエコーし、inode 番号もリストします。

set -- *
cmd="ls -ail"
for arg in "$@"
do echo "arg: $arg"
   escaped=$(sed -e "s/'/'\\''/g" -e "s/^/'/" -e "s/$/'/" <<< "$arg")
   cmd="$cmd $escaped"
done
echo "cmd: $cmd"
tcsh -c "$cmd"

ちょっとプレスト:

arg: ! % *
arg: $(pwd)
arg: `pwd`
arg: a b c
arg: d e f
cmd: ls -ail '! % *' '$(pwd)' '`pwd`' 'a b c' 'd e f'
! % *:
total 0
1640119 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:34 .
1640040 drwxr-xr-x  7 jleffler  staff  238 Aug 25 12:34 ..

$(pwd):
total 0
1640120 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:34 .
1640040 drwxr-xr-x  7 jleffler  staff  238 Aug 25 12:34 ..

`pwd`:
total 0
1640121 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:34 .
1640040 drwxr-xr-x  7 jleffler  staff  238 Aug 25 12:34 ..

a b c:
total 0
1640056 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:21 .
1640040 drwxr-xr-x  7 jleffler  staff  238 Aug 25 12:34 ..

d e f:
total 0
1640057 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:21 .
1640040 drwxr-xr-x  7 jleffler  staff  238 Aug 25 12:34 ..

まさに医者が注文したものです!しかし、私たちはまだ十分に残忍ではありませんでした: Knuth が言うように、コードをテストしているときは、本当に厄介な平均的な考え方に入る必要があるので、試してみましょう:

$ mkdir "O'Reilly's Books"
$ mkdir "' \` \""
$ mkdir '${HOME}' '$PATH' 'He said, "Don'\''t Do It!"'
$ ls -l
total 0
drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:34 ! % *
drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:34 $(pwd)
drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:45 $PATH
drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:45 ${HOME}
drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:43 ' ` "
drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:45 He said, "Don't Do It!"
drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:43 O'Reilly's Books
drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:34 `pwd`
drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:21 a b c
drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:21 d e f
$ 

結果は次のとおりです。

arg: ! % *
arg: $(pwd)
arg: $PATH
arg: ${HOME}
arg: ' ` "
arg: He said, "Don't Do It!"
arg: O'Reilly's Books
arg: `pwd`
arg: a b c
arg: d e f
cmd: ls -ail '! % *' '$(pwd)' '$PATH' '${HOME}' '''' ` "' 'He said, "Don'''t Do It!"' 'O'''Reilly'''s Books' '`pwd`' 'a b c' 'd e f'
Unmatched `.

それは私たちが望んでいたことではありません。ただし、問題の一部は、' cmd:' とタグ付けされた行の 4 つの一重引用符のシーケンスです。である必要があります''\''。したがって、sedスクリプトは十分に正確ではありません。

set -- *
cmd="ls -ail"
for arg in "$@"
do echo "arg: $arg"
   escaped=$(sed -e "s/'/'\\\\''/g" -e "s/^/'/" -e "s/$/'/" <<< "$arg")
   cmd="$cmd $escaped"
done
echo "cmd: $cmd"
tcsh -c "$cmd"

実行すると、次のようになります。

arg: ! % *
arg: $(pwd)
arg: $PATH
arg: ${HOME}
arg: ' ` "
arg: He said, "Don't Do It!"
arg: O'Reilly's Books
arg: `pwd`
arg: a b c
arg: d e f
arg: x.sh
cmd: ls -ail '! % *' '$(pwd)' '$PATH' '${HOME}' ''\'' ` "' 'He said, "Don'\''t Do It!"' 'O'\''Reilly'\''s Books' '`pwd`' 'a b c' 'd e f' 'x.sh'
1640231 -rw-r--r--  1 jleffler  staff  223 Aug 25 12:56 x.sh

! % *:
total 0
1640119 drwxr-xr-x   2 jleffler  staff   68 Aug 25 12:34 .
1640040 drwxr-xr-x  13 jleffler  staff  442 Aug 25 12:56 ..

$(pwd):
total 0
1640120 drwxr-xr-x   2 jleffler  staff   68 Aug 25 12:34 .
1640040 drwxr-xr-x  13 jleffler  staff  442 Aug 25 12:56 ..

$PATH:
total 0
1640176 drwxr-xr-x   2 jleffler  staff   68 Aug 25 12:45 .
1640040 drwxr-xr-x  13 jleffler  staff  442 Aug 25 12:56 ..

${HOME}:
total 0
1640175 drwxr-xr-x   2 jleffler  staff   68 Aug 25 12:45 .
1640040 drwxr-xr-x  13 jleffler  staff  442 Aug 25 12:56 ..

' ` ":
total 0
1640163 drwxr-xr-x   2 jleffler  staff   68 Aug 25 12:43 .
1640040 drwxr-xr-x  13 jleffler  staff  442 Aug 25 12:56 ..

He said, "Don't Do It!":
total 0
1640177 drwxr-xr-x   2 jleffler  staff   68 Aug 25 12:45 .
1640040 drwxr-xr-x  13 jleffler  staff  442 Aug 25 12:56 ..

O'Reilly's Books:
total 0
1640164 drwxr-xr-x   2 jleffler  staff   68 Aug 25 12:43 .
1640040 drwxr-xr-x  13 jleffler  staff  442 Aug 25 12:56 ..

`pwd`:
total 0
1640121 drwxr-xr-x   2 jleffler  staff   68 Aug 25 12:34 .
1640040 drwxr-xr-x  13 jleffler  staff  442 Aug 25 12:56 ..

a b c:
total 0
1640056 drwxr-xr-x   2 jleffler  staff   68 Aug 25 12:21 .
1640040 drwxr-xr-x  13 jleffler  staff  442 Aug 25 12:56 ..

d e f:
total 0
1640057 drwxr-xr-x   2 jleffler  staff   68 Aug 25 12:21 .
1640040 drwxr-xr-x  13 jleffler  staff  442 Aug 25 12:56 ..

十分ですか?近づいています。バックスラッシュを含むディレクトリ名についてはどうですか?

$ mkdir "a \\' \\\` \\$ b \\\" c"      # Make sure you do the exercise!
$ mkdir 'a \\'\'' \\\` \\$ b \\\" c'   # Make sure you do the exercise!
$ ls -li
total 8
1640119 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:34 ! % *
1640120 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:34 $(pwd)
1640176 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:45 $PATH
1640175 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:45 ${HOME}
1640163 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:43 ' ` "
1640177 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:45 He said, "Don't Do It!"
1640164 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:43 O'Reilly's Books
1640121 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:34 `pwd`
1640243 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:58 a \' \` \$ b \" c
1640259 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:01 a \\' \\\` \\$ b \\\" c
1640056 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:21 a b c
1640057 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:21 d e f
1640231 -rw-r--r--  1 jleffler  staff  223 Aug 25 12:56 x.sh
$ 

を にls -ail変更するls -dilと、出力は次のようになります。

$ bash x.sh
arg: ! % *
arg: $(pwd)
arg: $PATH
arg: ${HOME}
arg: ' ` "
arg: He said, "Don't Do It!"
arg: O'Reilly's Books
arg: `pwd`
arg: a \' \` \$ b \" c
arg: a \\' \\\` \\$ b \\\" c
arg: a b c
arg: d e f
arg: x.sh
cmd: ls -dil '! % *' '$(pwd)' '$PATH' '${HOME}' ''\'' ` "' 'He said, "Don'\''t Do It!"' 'O'\''Reilly'\''s Books' '`pwd`' 'a \'\'' \` \$ b \" c' 'a \\'\'' \\\` \\$ b \\\" c' 'a b c' 'd e f' 'x.sh'
1640119 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:34 ! % *
1640120 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:34 $(pwd)
1640176 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:45 $PATH
1640175 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:45 ${HOME}
1640163 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:43 ' ` "
1640177 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:45 He said, "Don't Do It!"
1640164 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:43 O'Reilly's Books
1640121 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:34 `pwd`
1640243 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:58 a \' \` \$ b \" c
1640259 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:01 a \\' \\\` \\$ b \\\" c
1640056 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:21 a b c
1640057 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:21 d e f
1640271 -rw-r--r--  1 jleffler  staff  223 Aug 25 13:03 x.sh
$ 

作業スクリプト

set -- *
cmd="ls -ail"
for arg in "$@"
do echo "arg: $arg"
   escaped=$(sed -e "s/'/'\\\\''/g" -e "s/^/'/" -e "s/$/'/" <<< "$arg")
   cmd="$cmd $escaped"
done
echo "cmd: $cmd"
tcsh -c "$cmd"

概要

ソリューションの重要な部分は次のとおりです。

  1. 引数を一重引用符で囲む必要があることを認識しています。
  2. 一重引用符をエスケープする方法を知っている。
  3. バックスラッシュをエスケープする方法を知る。
  4. テストを行うときは本当に残忍です!
  5. 以前にやったことがあると助かります...

ああファッツ!改行を含む引数をテストするのを忘れていました:

$ mkdir "a
> b
> c"
$ ls -li
total 8
1640119 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:34 ! % *
1640120 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:34 $(pwd)
1640176 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:45 $PATH
1640175 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:45 ${HOME}
1640163 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:43 ' ` "
1640177 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:45 He said, "Don't Do It!"
1640164 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:43 O'Reilly's Books
1640121 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:34 `pwd`
1640336 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:16 a?b?c
1640243 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:58 a \' \` \$ b \" c
1640259 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:01 a \\' \\\` \\$ b \\\" c
1640056 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:21 a b c
1640057 drwxr-xr-x  2 jleffler  staff   68 Aug 25 12:21 d e f
1640271 -rw-r--r--  1 jleffler  staff  223 Aug 25 13:03 x.sh
$

からの出力を解析しようとしてはならない理由がいくつかありますls。改行の代わりにクエスチョン マークが生成されました (これは Mac OS X 10.8.1 上であり、 GNUではありませんls。自宅でスコアを管理しているユーザー向けです。他のシステムでは動作が異なる場合があります)。

スクリプト ( x.sh) を実行すると、次のようになります。

$ bash x.sh
arg: ! % *
arg: $(pwd)
arg: $PATH
arg: ${HOME}
arg: ' ` "
arg: He said, "Don't Do It!"
arg: O'Reilly's Books
arg: `pwd`
arg: a
b
c
arg: a \' \` \$ b \" c
arg: a \\' \\\` \\$ b \\\" c
arg: a b c
arg: d e f
arg: x.sh
cmd: ls -dil '! % *' '$(pwd)' '$PATH' '${HOME}' ''\'' ` "' 'He said, "Don'\''t Do It!"' 'O'\''Reilly'\''s Books' '`pwd`' 'a'
'b'
'c' 'a \'\'' \` \$ b \" c' 'a \\'\'' \\\` \\$ b \\\" c' 'a b c' 'd e f' 'x.sh'
ls: a: No such file or directory
1640119 drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:34 ! % *
1640120 drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:34 $(pwd)
1640176 drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:45 $PATH
1640175 drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:45 ${HOME}
1640163 drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:43 ' ` "
1640177 drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:45 He said, "Don't Do It!"
1640164 drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:43 O'Reilly's Books
1640121 drwxr-xr-x  2 jleffler  staff  68 Aug 25 12:34 `pwd`
b: Command not found.
c: Command not found.
$

ここには複数の問題があります。スクリプトは、引数のsed各行を個別に処理しました。それは実際にはsed;を使用しても解決できません。または、おそらくより正確には、 を使用して解決したいものではありませんsed。ずいぶん前に、私は C プログラムを書いて、スクリプトがほぼescape行う仕事をしました。sed

#!/bin/bash

set -- *
escaped=$(escape "$@")
cmd="ls -dil $escaped"
echo "cmd: $cmd"
bash -c "$cmd"
tcsh -c "$cmd"

そこに呼び出しを追加したことに注意してくださいbash。出力は次のとおりです。

cmd: ls -dil '! % *' '$(pwd)' '$PATH' '${HOME}' ''\'' ` "' 'He said, "Don'\''t Do It!"' 'O'\''Reilly'\''s Books' '`pwd`' 'a
b
c' 'a b c' 'd e f' x.sh
178474064 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:38 ! % *
178474065 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:38 $(pwd)
178474219 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:38 $PATH
178474218 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:38 ${HOME}
178474170 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:38 ' ` "
178474220 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:38 He said, "Don't Do It!"
178474131 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:38 O'Reilly's Books
178474066 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:38 `pwd`
178474998 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:40 a?b?c
178473958 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:38 a b c
178473959 drwxr-xr-x  2 jleffler  staff   68 Aug 25 13:38 d e f
178475097 -rw-r--r--  1 jleffler  staff  115 Aug 25 13:41 x.sh
Unmatched '.
b: Command not found.
Unmatched '.

ワサップ?bashなどの Bourne シェルから派生した他のシェルはksh、ある行から始まり他の行に続く文字列で問題ありませんが、C シェルとその派生シェルはそうではありません。改行の前にバックスラッシュを要求します。したがって、 を使用するには、C シェルの出力を生成するtcshようにアップグレードする必要があります。escape難しいことではありませんが、実行する必要があります。おそらく、それはオプション-cであり、一般的な安全のために、呼び出しは次のようになります。

escaped=$(escape -c -- "$@")

"$@"の引数がそれ自体のオプションとして誤って解釈されるのを防ぐために二重ダッシュを使用しescapeます。これは、移植可能なファイル名文字セット以外の文字を含むファイル名を処理するスクリプトを作成するのが難しいことを示しています。幸いなことに、C シェルを頻繁に扱う必要はありません。インターフェイスの変更であるため、これを の一部にする予定はありませescapeん (現在のコードには独自のオプションがないため、 でダブル ダッシュ表記を使用しませんescape)。必要になれば、cescape無条件に C シェルをサポートするようになります。

于 2012-08-25T20:13:37.287 に答える
1

私自身の質問に答えるには...これが解決策です:

/bin/tcsh -c "ls "'$argv:q' "${@}"
于 2012-08-30T17:16:19.397 に答える