マイユースケース:
composer.json
ファイルにリストされているすべての開発パッケージを削除する必要があります。projectx/package-nice
という2つのパッケージがあるとしましょうprojecty/package-good
。削除するには、以下を実行する必要があります。
$ composer remove --dev projectx/package-nice projecty/package-good
そのため、パッケージリストを抽出するために次のコマンドを作成します。
echo $(composer show -s | grep -E "^[a-z]+/[0-9a-z_-]+" | awk '{print $1}' | xargs)
これにより、次のようにパッケージのリストが返されます。projectx/package-nice projecty/package-good
そのため、bashは戻り値を引用符で囲んだ単一の文字列として解釈するため、成功しないまま次のコマンドを実行してみました。
$ composer remove --dev $(composer show -s | grep -E "^[a-z]+/[0-9a-z_-]+" | awk '{print $1}' | xargs)
これは次のとおりです。
$ composer remove --dev "projectx/package-nice projecty/package-good"
それでは、私が何を間違っているのでしょうか?
編集する:
問題は解析にはありません。$()
スペースで区切られた予想値を返します。問題は、bashがこの戻り値を一意の値として解釈する理由です。
@MarcusMüllerが指摘したように、この問題は発生しないでください。私は走っています/etc
:
$ ls $(ls | head -n 2)
そして、実行されたコマンドはls file1 file2
代わりなので、ls "file1 file2"
なぜこれが起こるのか理解できません。おそらく作曲家はPHPで実行されるスクリプトであり、これが何かを邪魔しているからでしょうか?
ありがとうございます。
答え1
composer remove --dev $(composer show -s | grep -E "^[a-z]+/[0-9a-z_-]+" | awk '{print $1}' | xargs)
等しくない:
composer remove --dev "projectx/package-nice projecty/package-good"
その$(cmd)
部分は参照されず、リストコンテキストで分割+globの影響を受けるからです(ここでは単純なコマンドの引数から)。
変更しない限り、$IFS
(分ける部分)に空白文字が含まれているため、cmd
出力の場合はprojectx/package-nice projecty/package-good\n
合計に分割され、projectx/package-nice
別projecty/package-good
の引数としてに渡されますcomposer
。
ところで、改行文字もデフォルト値なので、$IFS
あなたのものxargs
(改行文字を空白に変換する意図のようです)は意味がありません。
シェルを使用する場合は、分割+globを使用するのではなく、ファイルの行を配列の個々の要素として読み取る方がbash
合理的です。readarray
readarray -t packages < <(
composer show -s | grep -Po '^\p{Ll}+/[\p{Ll}\d_-]\H*'
)
(( ${#packages[@]} == 0 )) ||
composer remove --dev "${packages[@]}"
Split + globを使用することもオプションですが、常にそうであるように、使用時に特定の要件に合わせて調整するのが最善です。
IFS=$'\n' # split on newline only
set -o noglob # disable the glob part which we don't want
packages=( $(cmd...) ) # split+glob, result assigned to an array
あなたの場合、出力にはcmd
スペースやタブを含めないでください。他の2つの文字は$IFS
デフォルト値inであるため、そのままbash
残すことができます$IFS
。
ただし、地球を含めることができます。たとえば、composer show -s
出力の場合はetc/p* blah blah
パイプラインが出力されetc/p*
、内部で実行されない場合/
はパイプラインは、、...に拡張されますset -o noglob
。etc/p*
etc/pam.conf
etc/passwd
etc/profile
分割+globを防止し、出力cmd
(コマンド置換によって削除された末尾の改行を除く)をコマンドの引数として1つだけ渡すには、二重引用符を使用します。
composer remove --dev "$(cmd)"
cmd
(パッケージが1つだけ出力される場合にのみ意味があります。)
Linuxでは、次のコマンドを使用して引数がコマンドに渡されるのを見ることができます。
strace -s999999 -qqfe execve the-command and its args
(またはstrace
シェルでコマンドを実行して、シェルexecve()
で実行されたすべてのシステムコールまたは生成されたすべてのプロセスを追跡します。)
たとえば、
スプリット+グローブ、デフォルトはIFSです。
bash-5.0$ strace -s999999 -qqfe execve true $(echo foo; echo foo bar)
execve("/usr/bin/true", ["true", "foo", "foo", "bar"], 0x7ffe1374cc50 /* 66 vars */) = 0
改行でのみ分割:
bash-5.0$ (IFS=$'\n'; strace -s999999 -qqfe execve true $(echo foo; echo; echo foo bar))
execve("/usr/bin/true", ["true", "foo", "foo bar"], 0x7ffd16c547f8 /* 66 vars */) = 0
(空白行は削除されました。)
glob部分の役割:
bash-5.0$ (strace -s999999 -qqfe execve true $(echo 'etc/p*'))
execve("/usr/bin/true", ["true", "etc/pam.conf", "etc/pam.d", "etc/papersize", "etc/parallel", "etc/passwd", "etc/passwd-", "etc/pcmcia", "etc/perl", "etc/php", "etc/pki", "etc/pm", "etc/pnm2ppa.conf", "etc/polkit-1", "etc/popularity-contest.conf", "etc/ppp", "etc/printcap", "etc/profile", "etc/profile.d", "etc/protocols", "etc/pulse", "etc/python2.7", "etc/python3", "etc/python3.8"], 0x7ffdc911f8a0 /* 66 vars */) = 0
次に修正されましたset -o noglob
:
bash-5.0$ (set -o noglob; strace -s999999 -qqfe execve true $(echo 'etc/p*'))
execve("/usr/bin/true", ["true", "etc/p*"], 0x7ffe9c278a50 /* 66 vars */) = 0
参照で分割+グローブを無効にします。
bash-5.0$ (IFS=$'\n'; strace -s999999 -qqfe execve true "$(echo foo; echo; echo foo bar; echo 'etc/p*')")
execve("/usr/bin/true", ["true", "foo\n\nfoo bar\netc/p*"], 0x7ffcf0e70d20 /* 66 vars */) = 0
では、zsh
glob部分ではなく引用符なしでコマンドを置き換えると、IFS分割部分のみが実行されます(kshは分割+globに加えて中括弧拡張も実行します)。また、zsh
パラメータ拡張フラグを使用して、コマンド置換の上に明示的な分割を適用することもできます。たとえば、出力を改行に分割するので、ここでは次のようにします。s
f
0
${(f)"$(cmd)"}
cmd
packages=( ${(f)"$(composer show -s | grep -Po '^\p{Ll}+/[\p{Ll}\d_-]\H*')"} )
(( $#packages == 0 )) || composer remove --dev $packages
$IFS
ワイルドカードを完全に変更または無効にする必要はありません。
¹ランダムな入力で失敗し、非効率的になることが多いため、これは間違った方法です。
答え2
あなたは解決策を持っています。コマンドは、echo
スペースで区切られた2つの文字列を返します。これをコマンドで包み込むと$()
(例えば、入れ子にすることができます)、あなたが望むものを得ることができます。
composer remove --dev $(echo $(composer show -s | grep -E "^[a-z]+/[0-9a-z_-]+" | awk '{print $1}' | xargs))