বাশ: নির্বাচনের ক্ষেত্রে হোয়াইটস্পেস-নিরাপদ পদ্ধতিগত ব্যবহার


12

এই ফাইলের নাম দেওয়া হয়েছে:

$ ls -1
file
file name
otherfile

bash এম্বেড করা সাদা স্থানের সাথে নিজেই পুরোপুরি সূক্ষ্মভাবে কাজ করে:

$ for file in *; do echo "$file"; done
file
file name
otherfile
$ select file in *; do echo "$file"; done
1) file
2) file name
3) otherfile
#?

যাইহোক, কখনও কখনও আমি প্রতিটি ফাইলের সাথে কাজ করতে বা এমনকি কঠোরভাবে ভিতরে আসতে চাই না $PWDযেখানে এটি findআসে Which

$ find -type f -name file\*
./file
./file name
./directory/file
./directory/file name

আমি এই স্ক্রিপ্টলেটের হুইস্পেস-সেফ ভার্সনকে একত্রিত করার চেষ্টা করছি যা এর ফলাফল আউটপুট নেবে findএবং এতে উপস্থাপন করবে select:

$ select file in $(find -type f -name file); do echo $file; break; done
1) ./file
2) ./directory/file

যাইহোক, এই ফাইলের নামগুলিতে সাদা স্থানের সাথে বিস্ফোরিত হয়:

$ select file in $(find -type f -name file\*); do echo $file; break; done
1) ./file        3) name          5) ./directory/file
2) ./file        4) ./directory/file  6) name

সাধারনত, আমি সাথে জগাখিচুড়ি করে এই কাছাকাছি পেতে হবে IFS। যাহোক:

$ IFS=$'\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
$ IFS='\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'

এর সমাধান কী?



1
যদি আপনি কেবলfind কোনও নির্দিষ্ট ফাইলের নামের সাথে মেলে তার সক্ষমতা ব্যবহার করছেন তবে আপনি 4 বা তার পরে সহজভাবে select file in **/file*(সেটিংয়ের পরে shopt -s globstar) ব্যবহার করতে পারেন bash
চিপনার

উত্তর:


14

আপনার যদি কেবল ফাঁকা স্থান এবং ট্যাবগুলি হস্তান্তর করতে হয় (এম্বেড করা নতুনলাইন নয়) তবে আপনি একটি অ্যারে যেমন পড়তে mapfile(বা এর প্রতিশব্দ, readarray) ব্যবহার করতে পারেন

$ ls -1
file
other file
somefile

তারপর

$ IFS= mapfile -t files < <(find . -type f)
$ select f in "${files[@]}"; do ls "$f"; break; done
1) ./file
2) ./somefile
3) ./other file
#? 3
./other file

আপনি যদি না হ্যান্ডল নতুন লাইন করতে হবে, এবং আপনার bashসংস্করণ একটি নাল-সীমা নির্দেশ করা উপলব্ধ mapfile1 , তাহলে আপনি যে পরিবর্তন করতে পারেন IFS= mapfile -t -d '' files < <(find . -type f -print0)। অন্যথায়, findএকটি readলুপ ব্যবহার করে নাল-সীমাবদ্ধ আউটপুট থেকে সমতুল্য অ্যারে সংগ্রহ করুন :

$ touch $'filename\nwith\nnewlines'
$ 
$ files=()
$ while IFS= read -r -d '' f; do files+=("$f"); done < <(find . -type f -print0)
$ 
$ select f in "${files[@]}"; do ls "$f"; break; done
1) ./file
2) ./somefile
3) ./other file
4) ./filename
with
newlines
#? 4
./filename?with?newlines

1-d বিকল্প যোগ করা হয়েছিল mapfileমধ্যে bashসংস্করণ 4.4 iirc



আসলে, mapfileআমার কাছেও এটি নতুন। যশ।
ডোপগোতি

while IFS= readসংস্করণ ব্যাশ v3 এর (যা আমাদের মধ্যে যারা MacOS ব্যবহার করার জন্য গুরুত্বপূর্ণ) ফিরে কাজ করে।
গর্ডন ডেভিসন

3
find -print0বৈকল্পিক জন্য +1 ; এটি একটি জ্ঞাত-ভুল সংস্করণের পরে রাখার জন্য গ্র্যাম্বল করুন এবং এটি কেবলমাত্র ব্যবহারের জন্য বর্ণনা করার জন্য যদি কেউ জানে যে তাদের নিউলাইনগুলি হ্যান্ডেল করা দরকার। যদি কেউ কেবল এমন জায়গাগুলিতে অপ্রত্যাশিত পরিস্থিতি পরিচালনা করে তবে কেউ কখনও অপ্রত্যাশিতভাবে একেবারেই পরিচালনা করবে না।
চার্লস ডাফি

8

এই উত্তরের যে কোনও ধরণের ফাইলের সমাধান রয়েছে । নতুন লাইন বা স্পেস সহ।
সাম্প্রতিক ব্যাশের পাশাপাশি প্রাচীন বাশ এমনকি পুরানো পোস্টিক্স শেলগুলির সমাধান রয়েছে solutions

এই উত্তরে নীচে তালিকাভুক্ত গাছ [1] পরীক্ষার জন্য ব্যবহৃত হয়।

নির্বাচন করা

selectকোনও অ্যারে দিয়ে কাজ করা সহজ :

$ dir='deep/inside/a/dir'
$ arr=( "$dir"/* )
$ select var in "${arr[@]}"; do echo "$var"; break; done

অথবা অবস্থানগত পরামিতি সহ:

$ set -- "$dir"/*
$ select var; do echo "$var"; break; done

সুতরাং, একমাত্র আসল সমস্যাটি হ'ল একটি ফাইলের তালিকা বা অবস্থানের প্যারামিটারের ভিতরে "ফাইলগুলির তালিকা" (সঠিকভাবে সীমিত)। পড়তে থাকুন।

সজোরে আঘাত

বাশ দিয়ে আপনি যে সমস্যাটি প্রতিবেদন করেছেন তা আমি দেখতে পাচ্ছি না। বাশ একটি প্রদত্ত ডিরেক্টরিতে অনুসন্ধান করতে সক্ষম:

$ dir='deep/inside/a/dir'
$ printf '<%s>\n' "$dir"/*
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>

অথবা, আপনি যদি একটি লুপ পছন্দ করেন:

$ set -- "$dir"/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>

নোট করুন যে উপরের সিনট্যাক্সটি কোনও (যুক্তিসঙ্গত) শেল (কমপক্ষে সিএস নয়) দিয়ে সঠিকভাবে কাজ করবে।

উপরের সিনট্যাক্সের একমাত্র সীমাটি অন্যান্য ডিরেক্টরিতে নেমে আসা।
তবে বাশ তা করতে পারে:

$ shopt -s globstar
$ set -- "$dir"/**/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>

কেবলমাত্র কিছু ফাইল নির্বাচন করতে (ফাইলগুলির মধ্যে শেষ হওয়া ফাইলগুলির মতো) কেবল * প্রতিস্থাপন করুন:

$ set -- "$dir"/**/*file
$ printf '<%s>\n' "$@"
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/zz last file>

বলিষ্ঠ

আপনি যখন শিরোনামে একটি "স্পেস- সেফ " রাখবেন, আমি ধরে নিতে যাচ্ছি আপনি যা বোঝাতে চেয়েছিলেন তা " শক্ত " ছিল।

স্পেসগুলি (বা নিউলাইনস) সম্পর্কে শক্তিশালী হওয়ার সহজ উপায় হ'ল ফাঁকা স্থান (বা নিউলাইন) থাকা ইনপুটটির প্রক্রিয়াকরণটিকে প্রত্যাখ্যান করা। শেলটিতে এটি করার একটি খুব সহজ উপায় হ'ল কোনও স্থানের সাথে কোনও ফাইলের নাম প্রসারিত হলে কোনও ত্রুটি সহ প্রস্থান করা। এটি করার বিভিন্ন উপায় রয়েছে তবে সর্বাধিক কমপ্যাক্ট (এবং পিক্সিক্স) (তবে একটি ডিরেক্টরি সামগ্রীর মধ্যে স্যুডডাইরেক্টরিজের নাম এবং ডট-ফাইলগুলি এড়ানো সহ সীমাবদ্ধ) হ'ল:

$ set -- "$dir"/file*                            # read the directory
$ a="$(printf '%s' "$@" x)"                      # make it a long string
$ [ "$a" = "${a%% *}" ] || echo "exit on space"  # if $a has an space.
$ nl='
'                    # define a new line in the usual posix way.  

$ [ "$a" = "${a%%"$nl"*}" ] || echo "exit on newline"  # if $a has a newline.

যদি ব্যবহৃত সমাধানটি সেই আইটেমগুলির মধ্যে কোনওর মধ্যে শক্ত হয় তবে পরীক্ষাটি সরিয়ে ফেলুন।

ব্যাশে, উপ-ডিরেক্টরিগুলি উপরে বর্ণিত ** দিয়ে একবারে পরীক্ষা করা যেতে পারে।

ডট ফাইল অন্তর্ভুক্ত করার কয়েকটি উপায় রয়েছে, পসিক্স সমাধানটি হ'ল:

set -- "$dir"/* "$dir"/.[!.]* "$dir"/..?*

অনুসন্ধান

যদি কোনও কারণে অবশ্যই ব্যবহার করা হয় তবে ডিলিমিটারটি একটি NUL (0x00) দিয়ে প্রতিস্থাপন করুন।

bash 4.4+

$ readarray -t -d '' arr < <(find "$dir" -type f -name file\* -print0)
$ printf '<%s>\n' "${arr[@]}"
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/file>

bash 2.05+

i=1  # lets start on 1 so it works also in zsh.
while IFS='' read -d '' val; do 
    arr[i++]="$val";
done < <(find "$dir" -type f -name \*file -print0)
printf '<%s>\n' "${arr[@]}"

POSIXLY

একটি বৈধ পসিক্স সমাধান তৈরি করতে যেখানে সন্ধানের কোনও NUL ডিলিমিটার নেই এবং পাঠের জন্য কোনও নেই -d(না -a) আমাদের সম্পূর্ণ ডিফেরেন্ট অ্যাপ্রোচ দরকার।

-execশেল থেকে একটি কল সহ আমাদের খুঁজে পেতে একটি জটিল ব্যবহার করতে হবে :

find "$dir" -type f -exec sh -c '
    for f do
        echo "<$f>"
    done
    ' sh {} +

বা, যা প্রয়োজন তা যদি একটি নির্বাচন হয় (নির্বাচন করা বাশের অংশ হয় তবে শ নয়):

$ find "$dir" -type f -exec bash -c '
      select f; do echo "<$f>"; break; done ' bash {} +

1) deep/inside/a/dir/file name
2) deep/inside/a/dir/zz last file
3) deep/inside/a/dir/file with a
newline
4) deep/inside/a/dir/directory/file name
5) deep/inside/a/dir/directory/zz last file
6) deep/inside/a/dir/directory/file with a
newline
7) deep/inside/a/dir/directory/file
8) deep/inside/a/dir/file
#? 3
<deep/inside/a/dir/file with a
newline>

[1] এই গাছটি (012 ডলার নতুন লাইনগুলি):

$ tree
.
└── deep
    └── inside
        └── a
            └── dir
                ├── directory
                   ├── file
                   ├── file name
                   └── file with a \012newline
                ├── file
                ├── file name
                ├── otherfile
                ├── with a\012newline
                └── zz last file

এই দুটি কমান্ড দিয়ে নির্মিত হতে পারে:

$ mkdir -p deep/inside/a/dir/directory/
$ touch deep/inside/a/dir/{,directory/}{file{,\ {name,with\ a$'\n'newline}},zz\ last\ file}

6

আপনি একটি লুপিং কনস্ট্রাক্টের সামনে কোনও ভেরিয়েবল সেট করতে পারবেন না, তবে আপনি এটি শর্তের সামনে সেট করতে পারেন। ম্যান পৃষ্ঠা থেকে বিভাগটি এখানে:

যে কোনও সাধারণ কমান্ড বা ফাংশনের পরিবেশকে উপরের প্যারামিটারগুলিতে বর্ণিত হিসাবে প্যারামিটার অ্যাসাইনমেন্ট সহ উপস্থাপিত করে অস্থায়ীভাবে বাড়ানো যেতে পারে।

(একটি লুপ একটি সহজ আদেশ নয় ।)

এখানে ব্যর্থতা এবং সাফল্যের পরিস্থিতি প্রদর্শনের জন্য ব্যবহৃত একটি সাধারণ নির্মাণ:

IFS=$'\n' while read -r x; do ...; done </tmp/file     # Failure
while IFS=$'\n' read -r x; do ...; done </tmp/file     # Success

দুর্ভাগ্যক্রমে আমি কোনও সম্পর্কিতটির প্রক্রিয়াকরণকে প্রভাবিত করার IFSপরে selectকনস্ট্রাক্টে কোনও পরিবর্তন এম্বেড করার কোনও উপায় দেখতে পাচ্ছি না $(...)। তবে IFSলুপের বাইরে সেট হওয়া রোধ করার মতো কিছুই নেই :

IFS=$'\n'; while read -r x; do ...; done </tmp/file    # Also success

এবং এটি এই নির্মাণ যা আমি দেখতে দেখতে কাজ করতে পারি select:

IFS=$'\n'; select file in $(find -type f -name 'file*'); do echo "$file"; break; done

যখন আত্মরক্ষামূলক কোড লেখা আমি সুপারিশ চাই যে ধারা হয় একটি subshell মধ্যে চালানো যাবে, অথবা IFSএবং SHELLOPTSসংরক্ষিত এবং ব্লক প্রায় পুনরুদ্ধার:

OIFS="$IFS" IFS=$'\n'                     # Split on newline only
OSHELLOPTS="$SHELLOPTS"; set -o noglob    # Wildcards must not expand twice

select file in $(find -type f -name 'file*'); do echo $file; break; done

IFS="$OIFS"
[[ "$OSHELLOPTS" !~ noglob ]] && set +o noglob

5
IFS=$'\n'এটি নিরাপদ ধরে নিলে তা ভিত্তিহীন। ফাইলের নামগুলি পুরোপুরি নতুন লাইনের আক্ষরিক ধারণ করতে সক্ষম।
চার্লস ডাফি

4
আমি উপস্থিত থাকা সত্ত্বেও, ফেস ভ্যালুতে কারওর সম্ভাব্য ডেটাসেট সম্পর্কে এই জাতীয় মতামত স্বীকার করতে দ্বিধায় দ্বিধা বোধ করছি। সবচেয়ে খারাপ তথ্য ক্ষতির ঘটনার জন্য আমি উপস্থিত হয়েছি এমন একটি ক্ষেত্রে যেখানে পুরানো ব্যাকআপগুলি সাফ করার জন্য দায়ী রক্ষণাবেক্ষণ স্ক্রিপ্টটি এমন একটি ফাইল সরিয়ে দেওয়ার চেষ্টা করেছিল যা পাইথন স্ক্রিপ্ট দ্বারা সি মডিউল ব্যবহার করে একটি খারাপ পয়েন্টার ডিरेফারেন্স দিয়ে এলোমেলো আবর্জনা ফেলে দেয় remove - একটি সাদা জায়গা থেকে পৃথক ওয়াইল্ডকার্ড সহ - নামে into
চার্লস ডাফি

2
শেল স্ক্রিপ্টটি তৈরি করা লোকেরা এই ফাইলগুলির ক্লিনআপ করছে যা উদ্ধৃতি দেয় না কারণ নামগুলি "সম্ভবত সম্ভব" না মেলাতে ব্যর্থ হয়েছিল [0-9a-f]{24}। গ্রাহক বিলিং সমর্থন করতে ব্যবহৃত ডাটা ব্যাকআপের টিবি হারিয়ে গেছে।
চার্লস ডাফি

4
সম্পূর্ণভাবে @ চার্লসডফির সাথে সম্মত হন। প্রান্তের কেসগুলি পরিচালনা না করা কেবলমাত্র তখনই ভাল যখন আপনি ইন্টারেক্টিভভাবে কাজ করছেন এবং আপনি কী করছেন তা দেখতে পাবেselectএটির নকশাটি স্ক্রিপ্টযুক্ত সমাধানগুলির জন্য, তাই এটি সর্বদা প্রান্তের কেসগুলি হ্যান্ডেল করার জন্য ডিজাইন করা উচিত।
ওয়াইল্ডকার্ড

2
@ ইল্কাচ্চু অবশ্যই - আপনি কখনই selectচালনার জন্য কমান্ড টাইপ করছেন এমন শেল থেকে কল করবেন না , তবে কেবল কোনও স্ক্রিপ্টে, যেখানে আপনি সেই স্ক্রিপ্টের দ্বারা প্রদত্ত একটি প্রম্পটের উত্তর দিচ্ছেন , এবং যেখানে স্ক্রিপ্টটি রয়েছে পূর্বনির্ধারিত লজিক (ফাইলের নামগুলি চালিত না হওয়াতে চালিত হওয়া চালিত হয়) চালিয়ে দেওয়া হয় ইনপুটটির উপর ভিত্তি করে।
চার্লস ডাফি

4

আমি এখানে আমার এখতিয়ারের বাইরে থাকতে পারি তবে সম্ভবত আপনি এই জাতীয় কিছু দিয়ে শুরু করতে পারেন, কমপক্ষে শ্বেত স্পেসে কোনও সমস্যা নেই:

find -maxdepth 1 -type f -printf '%f\000' | {
    while read -d $'\000'; do
            echo "$REPLY"
            echo
    done
}

মন্তব্যগুলিতে উল্লিখিত হিসাবে কোনও সম্ভাব্য মিথ্যা অনুমানগুলি এড়ানোর জন্য, সচেতন থাকুন যে উপরের কোডটি সমান:

   find -maxdepth 1 -type f -printf '%f\0' | {
        while read -d ''; do
                echo "$REPLY"
                echo
        done
    }

read -dএকটি চতুর সমাধান; এর জন্য ধন্যবাদ.
ডোপঘোতি

2
read -d $'\000'হয় ঠিক অভিন্ন করার read -d ''কিন্তু ব্যাশ এর ক্ষমতা (implying, ভুল, এটা স্ট্রিং মধ্যে আক্ষরিক NULs প্রতিনিধিত্ব করতে সক্ষম) সম্পর্কে ভাবেন বিভ্রান্তিকর জন্য। চালান s1=$'foo\000bar'; s2='foo', এবং তারপরে দুটি মানগুলির মধ্যে পার্থক্য করার উপায় খুঁজে বের করার চেষ্টা করুন। (ভবিষ্যতের সংস্করণ সঞ্চিত মানটির সমতুল্য করে কমান্ড প্রতিস্থাপনের আচরণের সাথে স্বাভাবিক হতে পারে foobar, তবে আজকের দিনটি এটি নয়)।
চার্লস ডাফি
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.