スペースとワイルドカードを含むパスを含む文字列でのBashスクリプトエラー

Bashスクリプトの基本を理解するのに問題があります。これまでのところ、次のようになっています。

#!/bin/bash FILES="/home/john/my directory/*.txt" for f in "${FILES}" do echo "${f}" done 

やりたいのは、すべての.txtファイルを一覧表示することだけです。 forループ内にあるので、それらを使って作業を行うことができます。ただし、my directoryのスペースとはうまく再生されていません。二重引用符を付けて、または付けずに、変数名に中括弧を付けて、または付けずに使用してみましたが、それでもすべての.txtファイルを印刷できません。

これは非常に基本的なことですが、疲れていて正直に考えることができないため、まだ苦労しています。

何が悪いのですか?

応募できました。ファイルにスペースやアスタリスクがない場合の上記のスクリプト…それを機能させるには、二重引用符と中括弧を使用するかどうかを試してみる必要がありました。しかし、スペースとアスタリスクの両方があると、すべてが台無しになります。

回答

引用符の中に、*はファイルのリストに展開されません。このようなワイルドカードを正常に使用するには、引用符の外側にある必要があります。

ワイルドカードが展開された場合でも、式"${FILES}"は、単一の文字列になります。ファイルのリスト。

有効なアプローチの1つは、次のとおりです。

#!/bin/bash DIR="/home/john/my directory/" for f in "$DIR"/*.txt do echo "${f}" done 

上記では、スペースなどの難しいファイル名文字は正しく処理されます。

より高度なアプローチでは、bash配列を使用できます。

#!/bin/bash FILES=("/home/john/my directory/"*.txt) for f in "${FILES[@]}" do echo "${f}" done 

この場合、FILESはファイル名の配列です。定義を囲むパレンはそれを配列にします。 *は引用符の外にあることに注意してください。構成"${FILES[@]}"は特殊なケースです。これは、各文字列がファイル名の1つである文字列のリストに展開されます。スペースやその他の難しい文字を含むファイル名は正しく処理されます。

コメント

  • うまくいきました
  • ' 'このようなパスを関数に渡す場合は、変数を独自に引用する必要があることに注意してください。 大きな文字列の一部として連結するのではなく:for f in "$DIR"/*.txt =細かいfor f in "$DIR/*.txt" =ブレーク

回答

John1024で示されている配列を使用する方がはるかに理にかなっていますが、ここでは、split + glob演算子を使用することもできます(そのままにします)。引用符で囲まれていないスカラー変数)。

その演算子のglob部分のみが必要なため、 split 部分を無効にする必要があります。

#! /bin/sh - # that also works in any sh, so you don"t even need to have or use bash file_pattern="/home/john/my directory/*.txt" # all uppercase variables should be reserved for environment variables IFS="" # disable splitting for f in $file_pattern # here we"re not quoting the variable so # we"re invoking the split+glob operator. do printf "%s\n" "$f" # avoid the non-reliable, non-portable "echo" done 

回答

できることは、引用符の外にワイルドカード文字のみを残すことです。
次のようなもの:
「スペースのあるファイル」* “の場合。 txt “
do
処理
done
ワイルドカード自体がスペースに展開される場合は、ファイルのリストを生成するためにls -lを使用するなど、行ごとのアプローチを使用する必要はありません。 bashreadを使用して各ファイルを取得します。

回答

単一行ベースのソリューション(ターミナルで実行):
/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; }; done; unset f;

/ OPの場合は、"./"

スクリプトファイルで使用するには:

 #!/bin/bash /usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done; unset f;  

上記の機能は、この(推奨される)方法でも実現できます:

 #!/bin/bash while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done < <(/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0); unset f;  

簡単な説明:

"./":これは現在のディレクトリです。ディレクトリパスを指定します。
-not -type d:ここでは、-notがディレクトリパスをスキップするように構成しています。次に言及するタイプ、&次に言及する-typed =ディレクトリであるため、スキップされますディレクトリ。ファイルをスキップするには、dの代わりにfを使用します。シンボリックリンクファイルをスキップするには、dの代わりにlを使用します。
-maxdepth 1:現在の(別名:1)ディレクトリレベル内のファイルのみを検索するように構成しています。各&の最初のサブディレクトリレベル内のファイルを検索するには、maxdepthを2に設定します。-maxdepthを使用しない場合は、再帰的に(サブディレクトリ内で)検索します。
-iname "*.jpg":ここで-inameは、ファイルを検索して無視するように構成しています(上/下) -ファイル名/拡張子の場合。 -nameは大文字と小文字を区別しません。 -lnameはシンボリックリンクを検索します。等
-print0:現在のファイルのパス名を標準出力に出力し、その後にASCII NUL文字(文字コード0)を出力します。後でwhilereadを使用して検出します。
IFS=:ここでは、ファイル名がスペースで終わる場合に使用されます。見つかった各ファイル名を検出するために、IFSでNUL / "" / \0を使用しています。 " find "は、

-print0によって生成されます。
read -r -d $"\0" fileName$"\0""" / NULです。 -rは、ファイル名に円記号が付いている場合に使用されました。
読み取り [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name。 ..] […]
-rバックスラッシュはエスケープ文字として機能しません。バックスラッシュは行の一部と見なされます。特に、バックスラッシュと改行のペアを行の継続として使用することはできません。
done < <(...):プロセス-ここで使用される置換/ " find "の出力を"読み取り of " while "-ループ。詳細:https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html、https://wiki.bash-hackers.org/syntax/expansion/proc_subst、https://tldp.org/LDP/abs/html/abs-guide.html#PROCESS-SUB、https://tldp.org/LDP/abs/html/abs-guide.html#PSUBP


@ John1024による他の回答では、 find "、外部ユーティリティ。
" find "は非常に効果的です&高速です。処理するファイルが多すぎる場合に使用します。

@ John1024のソリューションではディレクトリにファイルがない場合のmatching-rule行。したがって、[ ! -e "${f}" ]...行の下でそれをスキップします。
これは、ターミナルで直接使用する単一行のソリューションです。
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;

スクリプトは次のとおりです:

 #!/bin/bash DIR="/home/john/my directory/"; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; # your codes/commands, etc }; done; unset DIR;  

注:DIRのディレクトリの末尾に"/"スラッシュ(ディレクトリインジケータ)がある場合は、matching-rule 、ここでも"/"を使用する必要はありません。
または、反対の操作を行います。DIRでは"/"を最後に使用しないでください。したがって、マッチングルールで使用します"$DIR"/*.txt


[ ! -e "${f}" ]...コードは、 shell-option (別名:" shopt ")が使用または有効化されている:
shopt -s nullglob

shoptの状態がスクリプトによって変更された場合、他のbashベースのスクリプトプログラムでは、予期しない/予期しない問題が発生します。

を使用するすべてのスクリプトで一貫した動作をするbash、bash-shell-optionの状態はスクリプト内に記録/保存する必要があります。スクリプトで主要な機能の使用が終了したら、そのshell-optionを以前の設定に戻す必要があります。

バックティックを使用します(別名:アクサングラーブ、別名:バッククォート)`...` コマンド置換(内部bashコマンドコードなど)。新しいサブシェルを生成しません。バックスラッシュの文字通りの意味を保持し、より広いサポート(別名:移植性)などを実現します。バックティックベースの内部bashコマンドなどは、スクリプトなどと同じシェルで実行できることが多いため、少し高速です&より優れており、ここで扱っている目的にも適しています。 $(...)コマンド置換を好む場合は、それを使用してください。誰でも自由に&好きなものを選択したり、避けたりすることができます。詳細:ここ

上記のスクリプトが再び表示されます。今回は、スクリプトを終了する前に、ショップの以前の設定を復元した&:

 #!/bin/bash DIR="/home/john/my directory/"; ub="/usr/bin"; # shopt = shell-option(s). # Setting-up "previous-nullglob" state to "enabled"/"on"/"1": p_nullglob=1; # The "shopt -s" command output shows list of enabled shopt list, so if # nullglob is NOT set to ON/enabled, then setting "previous_nullglob" as 0 [ "`shopt -s | ${ub}/grep nullglob`" == "" ] && p_nullglob=0; # Enabling shell-options "nullglob": shopt -s nullglob; # previous code, but without the extra checking [ ! -e "${f}" ]... line: for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; # As we have utilized enabled nullglob shopt, its now in enabled state, # so if previously it was disabled only-then we will disable it: [ "$p_nullglob" -eq "0" ] && shopt -u nullglob; unset DIR; unset p_nullglob ub;  

shopt -p shoptNameの出力(例:shopt -p dotglob)は
次のいずれかになります:shopt -u shoptNameuunset / disabled / off / 0
または、これ:shopt -s shoptNamesset / enabled / on / 1
位置です文字列の"s"または"u"は常に7にあります(bashでは、文字列の文字位置は0から始まります。つまり、文字列の最初の文字は0
位置にあります。この"u"または変数に保存して、以前の状態を復元できるようにします。
そして、この(上記の)方法を適用してshoptの状態を保存/復元すると、次のことができます。外部ツール"grep"の使用は避けてください。

"."

ファイルを表示するには

、つまり、非表示の"txt"ファイルを表示するには、"dotglob"ショップを有効にする必要があります。

したがって、今回は以下に、"dotglob"が含まれています&は、非表示の"txt"ファイルを表示できるようになっています。

 #!/bin/bash DIR="/home/john/my directory/"; p_nullglob="u"; pSS="`shopt -p nullglob`"; [ "${pSS:7:1}" == "s" ] && p_nullglob="s"; p_dotglob="u"; pSS="`shopt -p dotglob`"; [ "${pSS:7:1}" == "s" ] && p_dotglob="s"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; [ "$p_nullglob" == "u" ] && shopt -u nullglob; [ "$p_dotglob" == "u" ] && shopt -u dotglob; unset DIR; unset p_nullglob p_dotglob pSS;  

ショップを保存/復元するもっと簡単な方法がありますオプション/値。
Isaac ここに投稿、環境を保存および復元する方法/ Shopt変数/オプションの状態/値。

" nullglob "のshopt状態を保存しています:

... # your primary-function codes/commands, etc lines
" nullglobの以前のショップ状態を復元"、スクリプトを終了する前:
eval "$p_nullglob" ;

複数のショップ状態を次の方法で保存できます:
p_multipleShopt="`shopt -p nullglob dotglob`";
および復元プロセスは以前と同じです:
eval "$p_multipleShopt" ;

すべてのショップの状態を次のように保存します:
p_allShopt="`shopt -p`";
復元プロセスは以前と同じです:
eval "$p_allShopt" ;

bashベースの別のソリューションは次のとおりです。

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; eval "$p_allShopt" ; unset DIR p_allShopt;  

変数"$p_allShopt"はユーザーから提供されたデータまたはそうでないデータを保持していないため、上記ではevalを使用しても安全です。サニタイズされ、その変数はbash内部コマンドshoptの出力を保持しています。
それでもevalを避けたい場合は、以下を使用してください。 lution:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; while IFS= read -a oneLine ; do { ${oneLine} ; }; done < <(echo "$p_allShopt") ; unset DIR p_allShopt oneLine;  

注目すべき(その他の)いくつかの&関連する SHOPT は、次のとおりです。

  • nocaseglob:設定されている場合、Bashはファイル名の展開を実行するときに大文字と小文字を区別しない方法。
  • nocasematch:設定されている場合、Bashは、caseまたは

条件付きコマンド、パターン置換ワード展開の実行時、またはプログラム可能な補完の一部として可能な補完をフィルタリングする場合。

  • dotglob:設定されている場合、Bashには<で始まるファイル名が含まれます。ファイル名展開の結果のdivid = "1ceaafe854">
  • 。ファイル名‘.’‘..’は、dotglobが設定されている場合でも、常に明示的に一致する必要があります。

  • nullglob:設定されている場合、Bashでは、ファイルに一致しないファイル名パターンを、それ自体ではなくnull文字列に展開できます。
  • extglob:設定されている場合、上記の拡張パターンマッチング機能(パターンマッチング)が有効になっています。
  • globstar:設定されている場合、ファイル名拡張コンテキストで使用されるパターン‘**’は、すべてのファイルと一致し、 0個以上のディレクトリとサブディレクトリ。パターンの後に‘/’が続く場合、一致するのはディレクトリとサブディレクトリのみです。
  • 回答

    ファイルのセットを処理する場合は、名前にスペースがあるか、他のスケープコードが含まれている可能性があるため、for loopまたはfind command IFS bash env variableを:

    IFS=$(echo -en "\n\b") 

    コメントを残す

    メールアドレスが公開されることはありません。 * が付いている欄は必須項目です