Bashがコマンドの出力を引用符付き文字列として解釈するにはどうすればよいですか?

Bashがコマンドの出力を引用符付き文字列として解釈するにはどうすればよいですか?

グラフィックIU(私の場合はmacOSのFinder)からファイルを選択するプログラムがあります。出力は次のとおりです

'/tmp/file number one.txt' '/tmp/file number two.txt'

名前に空白文字があるため、ファイル名は '(シングルストレートチェック)で囲まれます.

bashのコマンド置換(例えばls -lコマンド)でこのコマンドの出力を使用すると、すべてが混乱します。テストのために、上記の行を単純な1行のテキストファイルに入れ、それをコマンドラインの代替手段として使用しました。

$ cat /tmp/files.txt
'/tmp/file number one.txt' '/tmp/file number two.txt'
$ ls -l $(</tmp/files.txt)
ls: "'/tmp/file: No such file or directory
ls: '/tmp/file: No such file or directory
ls: number: No such file or directory
ls: number: No such file or directory
ls: one.txt': No such file or directory
ls: two.txt'": No such file or directory

ファイル名文字列を変数に割り当てて使用する場合も同じことが起こります。

$ xxx="'/tmp/file number one.txt' '/tmp/file number two.txt'"
$ ls -l $xxx
ls: '/tmp/file: No such file or directory
ls: '/tmp/file: No such file or directory
ls: number: No such file or directory
ls: number: No such file or directory
ls: one.txt': No such file or directory
ls: two.txt': No such file or directory

この問題を解決する方法を知っていますか?エスケープされたファイル名をコマンドラインに直接コピーすると、期待どおりに機能します。

$ ls -l '/tmp/file number one.txt' '/tmp/file number two.txt'
-rw-r--r--  1 tester  wheel     0B Jul 17 17:21:11 2021 /tmp/file number one.txt
-rw-r--r--  1 tester  wheel     0B Jul 17 17:21:16 2021 /tmp/file number two.txt

私の究極の目標は、現在のFinderの選択(コンパイルされたApplescriptを介して得られた)をbashで使用できるようにすることです。たとえば、lsファイルリストやその他のファイル処理コンテンツを使用できます。tarcpmv

答え1

遷移がオプションの場合は、zshこの目的のために設計されたzおよびパラメータ拡張フラグを使用できます。Q

file_content=$(</tmp/files.txt)
quoted_strings=(${(z)file_content})
strings_with_one_layer_of_quotes_removed=("${(Q@)quoted_strings}")
ls -ld -- "$strings_with_one_layer_of_quotes_removed[@]"

または、一度にすべての作業を実行してください。

ls -ld -- "${(Q@)${(z)$(</tmp/files.txt)}}"

ファイルで参照されている構文が同じであるとしますzsh

Z解析の実行方法を調整するには、パラメータの拡張も参照してください。たとえば、ファイルに#無視する必要があるコメントが含まれていて、2行以上の場合は、次のことを行う必要があります。

ls -ld -- "${(Q@)${(Z[Cn])$(</tmp/files.txt)}}"

info zsh flags詳細より。


zsh¹今、最新バージョンのmacOSでは、デフォルトのインタラクティブシェルと聞きました。

答え2

文字通り単一引用符を含む次の文字列があるとします。

'/tmp/file number one.txt' '/tmp/file number two.txt'

コマンドラインの一部としてインラインで提供すると正常に機能しますが、拡張機能で提供すると正しく機能しないことがわかります。変数拡張かコマンド置換かは重要ではなく、ルールは両方とも同じです。引用符のない拡張は単語分割を行いますが、スペースの分割はとの間で分割されるため、ここでは/tmp/file実行numberしたくありません。引用符付きの拡張は分割を実行しませんが、2つの中間の単一引用符の間を分割したい場合があるため、そうしたくありません。しかも別の事実がありますが、拡張で生成された引用は何も引用しません。。だから私たちは何か他のことをしなければなりません。

出力がシェル構文として知られていて安全であると仮定すると、シェルに次のように引用符をeval解釈するために別の処理ラウンドを実行させることができます。

#!/bin/bash
input="'/tmp/file number one.txt' '/tmp/file number two.txt'"
eval "ls -ld -- $input"

または、後で使用できるように配列に入れます。

#!/bin/bash
input="'/tmp/file number one.txt' '/tmp/file number two.txt'"
eval "files=($input)"
for f in "${files[@]}"; do
    printf "<%s>\n" "$f"
done

実行される文字列にeval引用符や二重引用符なしでコマンド置換が含まれている場合(たとえば、では/dir/$(uname -a)ない'/dir/$(uname -a)')、シェルは〜する処理の実行に関連するコマンドですeval。同様に、文字列に引用符のない文字列が含まれている場合、)配列の割り当ては終了します。したがって、自分が制御できるソースにのみ使用するのが最善です。

また、引用符を処理する前に文字列を分割してワイルドカードとして処理したくevalないので、文字列の周囲に二重引用符を使用する必要があります。eval


xargs引用符を解釈しますが、デフォルトでは引用符付き文字列を使用するなど、拡張を処理しない他のツールを使用する方法があります。たとえば、次はprintf各ファイル名に対して別々の引数として1を実行します。

printf '%s\n' "$input" | xargs printf ":%s:\n"

または、ls以下を実行してください。

printf '%s\n' "$input" | xargs ls -ld --

あるいは、xargsより単純な形式でファイル名を印刷してから、シェルの配列にロードできるプログラムを実行することもできます。 (これは少し逆さまですが、Bashに拡張ではなく参照処理のみを実行させる方法はわかりません。)

#!/bin/bash
readarray -td '' files < <(
  printf '%s\n' "$input" | xargs printf "%s\0")
for f in "${files[@]}"; do
    printf "<%s>\n" "$f"
done

(ここではprintfNULバイトで終わる文字列が出力されます。²readarray -td ''はこの形式の出力を期待しています。NULはファイル名には現れない唯一の値です。これはあいまいではなく比較的単純な形式です。)

ただし、これはxargsシェルとは異なる正確な引用規則を理解していることに注意してください。$'...'Bashを含む改行文字を含む値を出力するために使用する引用スタイルがわからず、二重引用符内のバックスラッシュを認識しません。 4 ...しかし、Finderの出力が一重引用符である場合(およびバックスラッシュが難しい一重引用符を引用してください。おそらく大丈夫でしょう。


1 シェルの組み込みでprintfはないスタンドアロンユーティリティ、空の入力でも少なくとも1回(一部BSDを除く)、リストが大きい場合複数回可能printf

²bash 4.4以降が必要です。

³ 1990年代 ksh93によって導入されました

1970年代後半にPWB UnixにPWB 4.4が登場したとき、引用xargs構文はBourne以前のバージョン(Mashey Shell)の構文と似ていましたが、shBourneシェルではなくksh93やbashは言うまでもありません。

答え3

最良の選択はNULで区切られた出力が生成されるように、役に立たないファイルのリストを生成するすべての項目を変更します。(なぜならNULはただパス/ファイル名には表示できない文字で、有効な文字を含むすべてのファイル名を処理することを保証する唯一の区切り文字です。これが不可能な場合は、NUL区切り形式に変換して「修正」を一緒にまとめることができます。

次のPerlの1行コードは、(ほとんど)ファイルを引用符なしでNULで区切られたファイル名に変換します。

perl -0 -pe "s/'\s+'/\0/sg; s/^'|'\$//sg; s/\x0d?\x0a\$//" file.txt

最初の正規表現はシーケンスをNUL文字に置き換えますsingle-quote, one-or-more whitespace chars, single-quote(カンマとスペースはパターンの一部ではなく、単に英語リストの区切り文字にすぎません). 2番目の正規表現は入力の先頭と末尾の引用符を削除し、3番目の正規表現は「行」の終わりからLFまたはCRLFを削除します。

これはまだ完璧とは遠い- ファイル名に一重引用符またはLFを含める必要があるかどうかを100%確信できないため、一部の入力は変更できません(したがって、NULで区切られたファイルで始めるのが正しい解決策です。後で合わせようとしないでください)。

たとえば、ファイル名の先頭または末尾に一重引用符を含むファイル名がある場合、一重引用符が含まれていて、後に1つ以上の空白文字が含まれていて、後に別の一重引用符がある場合は失敗します(例:) -最初の正規表現のグローバル修飾子(最初の正規表現だけでなく、入力のすべてのファイル名と一致する必要がある)' 'のため、これらはすべてNULに置き換えられます。/g私がまだ考えていない別のコーナーケースがあるかもしれません。

出力を別のファイルにリダイレクトしたり、に供給したりxargs -0rreadarraybashの組み込みとプロセスの置き換えと一緒に使用して配列を埋めることができます。

readarray -d '' files < <(perl -0 -pe "s/'\s+'/\0/sg;
                                       s/^'|'\$//sg;
                                       s/\x0d?\x0a\$//" file.txt)

xxd出力を(hdまたは同様の16進ダンププログラム)にパイプすると、hexdumpNULで区切られていることがわかります。

00000000: 2f74 6d70 2f66 696c 6520 6e75 6d62 6572  /tmp/file number
00000010: 206f 6e65 2e74 7874 002f 746d 702f 6669   one.txt./tmp/fi
00000020: 6c65 206e 756d 6265 7220 7477 6f2e 7478  le number two.tx
00000030: 74                                       t

関連情報