شرح - bash معنى




كيف يمكنني تقسيم سلسلة في محدد في Bash؟ (20)

إجابة متوافقة

إلى هذا السؤال ، هناك بالفعل الكثير من الطرق المختلفة للقيام بذلك في bash . لكن لباش العديد من المميزات الخاصة ، ما يطلق عليه bashism التي تعمل بشكل جيد ، لكن ذلك لن ينجح في أي shell أخرى.

على وجه الخصوص ، المصفوفات ، الصفيف الارتباطي ، واستبدال النمط هي bashisms النقي وقد لا تعمل تحت قذائف أخرى.

في ديبيان جنو / لينكس ، هناك غلاف قياسي يسمى dash ، لكنني أعرف العديد من الأشخاص الذين يحبون استخدام ksh .

وأخيرًا ، في حالة صغيرة جدًا ، توجد أداة خاصة تسمى busybox مع مترجم busybox الخاص به ( ash ).

السلسلة المطلوبة

نموذج سلسلة في سؤال SO هو:

IN="[email protected];[email protected]"

نظرًا لأن هذا قد يكون مفيدًا في المسافات البيضاء ، وحيث إن المسافات البيضاء يمكن أن تعدّل نتيجة الروتين ، فإنني أفضل استخدام سلسلة العينة هذه:

 IN="[email protected];[email protected];Full Name <[email protected]>"

تقسيم السلسلة استنادًا إلى المحدد في bash (الإصدار> = 4.2)

ضمن bash pure ، قد نستخدم المصفوفات و IFS :

var="[email protected];[email protected];Full Name <[email protected]>"

oIFS="$IFS"
IFS=";"
declare -a fields=($var)
IFS="$oIFS"
unset oIFS

IFS=\; read -a fields <<<"$var"

لا يؤدي استخدام بناء الجملة الحالي ضمن bash الأخير إلى تغيير $IFS لجلسة العمل الحالية ، ولكن فقط للأمر الحالي:

set | grep ^IFS=
IFS=$' \t\n'

يتم الآن تقسيم سلسلة var ويتم تخزينها في صفيف ( fields مسمى):

set | grep ^fields=\\\|^var=
fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")
var='[email protected];[email protected];Full Name <[email protected]>'

يمكننا طلب محتوى متغير مع declare -p :

declare -p var fields
declare -- var="[email protected];[email protected];Full Name <[email protected]>"
declare -a fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")

read هي أسرع طريقة للقيام بالانقسام ، لأنه لا يوجد شوك ولا موارد خارجية تسمى.

من هناك ، يمكنك استخدام البنية اللغوية التي تعرفها بالفعل لمعالجة كل حقل:

for x in "${fields[@]}";do
    echo "> [$x]"
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

أو إسقاط كل حقل بعد المعالجة (أحب هذا النهج المتغير ):

while [ "$fields" ] ;do
    echo "> [$fields]"
    fields=("${fields[@]:1}")
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

أو حتى للطباعة البسيطة (بناء الجملة الأقصر):

printf "> [%s]\n" "${fields[@]}"
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

تقسيم السلسلة على أساس محدد في shell

ولكن إذا كنت تكتب شيئًا قابلاً للاستخدام تحت العديد من القذائف ، فعليك عدم استخدام bashisms .

هناك بنية ، تستخدم في العديد من الأصداف ، لتقسيم سلسلة عبر التواجد الأول أو الأخير لسلسلة فرعية:

${var#*SubStr}  # will drop begin of string up to first occur of `SubStr`
${var##*SubStr} # will drop begin of string up to last occur of `SubStr`
${var%SubStr*}  # will drop part of string from last occur of `SubStr` to the end
${var%%SubStr*} # will drop part of string from first occur of `SubStr` to the end

(في عداد المفقودين من هذا هو السبب الرئيسي لنشر جوابي ؛)

كما أشار إلى: Score_Under :

# و % تحذف أقصر سلسلة مطابقة ممكنة ، و

## و %% أطول فترة ممكنة.

يعمل هذا البرنامج النصي الصغير بشكل جيد تحت bash و dash و ksh و busybox وقد تم اختباره تحت busybox Mac-OS:

var="[email protected];[email protected];Full Name <[email protected]>"
while [ "$var" ] ;do
    iter=${var%%;*}
    echo "> [$iter]"
    [ "$var" = "$iter" ] && \
        var='' || \
        var="${var#*;}"
  done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

إستمتع!

لدي هذه السلسلة مخزنة في متغير:

IN="[email protected];[email protected]"

الآن أود تقسيم السلاسل بواسطة ; محدد بحيث يكون لدي:

ADDR1="[email protected]"
ADDR2="[email protected]"

لا أحتاج بالضرورة إلى ADDR1 و ADDR2 . إذا كانت عناصر صفيف أفضل.

بعد الاقتراحات من الإجابات أدناه ، انتهى بي الأمر بما يلي هو ما كنت عليه بعد:

#!/usr/bin/env bash

IN="[email protected];[email protected]"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

انتاج:

> [[email protected]]
> [[email protected]]

كان هناك حل يتضمن إعداد Internal_field_separator (IFS) إلى ; . لست متأكداً مما حدث مع هذه الإجابة ، كيف تعيد تعيين IFS إلى الوضع الافتراضي؟

RE: IFS الحل ، حاولت هذا ويعمل ، IFS القديم ومن ثم استعادته:

IN="[email protected];[email protected]"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

راجع للشغل ، عندما حاولت

mails2=($IN)

حصلت فقط على السلسلة الأولى عند طباعتها في حلقة ، دون قوسين حول $IN يعمل.


أﻋﺗﻘد أن AWK ھو اﻷﻣر اﻷﻓﺿل واﻟﮐﻔﺎءة ﻟﺣل ﻣﺷﮐﻟﺗك. يتم تضمين AWK في Bash بشكل افتراضي في كل توزيعات Linux تقريبًا.

echo "[email protected];[email protected]" | awk -F';' '{print $1,$2}'

سنعطي

[email protected] [email protected]

بالطبع يمكنك تخزين كل عنوان بريد إلكتروني عن طريق إعادة تعريف حقل الطباعة awk.




بصرف النظر عن الإجابات الرائعة التي تم توفيرها بالفعل ، إذا كان الأمر مجرد مسألة طباعة البيانات التي قد تفكر في استخدامها awk :

awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"

هذا يحدد فاصل الحقل إلى ; ، بحيث يمكن تنفيذ حلقة عبر الحقول باستخدام حلقة for وطباعتها وفقًا لذلك.

اختبار

$ IN="[email protected];[email protected]"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [[email protected]]
> [[email protected]]

مع مدخلات أخرى:

$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c   d;e_;f"
> [a]
> [b]
> [c   d]
> [e_]
> [f]

تقوم الدالة Bash / zsh التالية بتقسيم الوسيطة الأولى الخاصة بها في المحدد المعطى بواسطة الوسيطة الثانية:

split() {
    local string="$1"
    local delimiter="$2"
    if [ -n "$string" ]; then
        local part
        while read -d "$delimiter" part; do
            echo $part
        done <<< "$string"
        echo $part
    fi
}

على سبيل المثال ، الأمر

$ split 'a;b;c' ';'

عائدات

a
b
c

على سبيل المثال ، قد يتم توجيه هذا الإخراج إلى أوامر أخرى. مثال:

$ split 'a;b;c' ';' | cat -n
1   a
2   b
3   c

بالمقارنة مع الحلول الأخرى المقدمة ، هذه الميزة لديها المزايا التالية:

  • لم يتم تجاوز IFS : نظرًا لوجود تحجيم ديناميكي للمتغيرات المحلية ، فإن تجاوز IFS عبر حلقة يؤدي إلى تسرب القيمة الجديدة إلى استدعاءات دالة تم تنفيذها من داخل الحلقة.

  • لا يتم استخدام الصفائف: يتطلب قراءة سلسلة إلى صفيف باستخدام read العلامة -a في Bash و -A في zsh.

إذا لزم الأمر ، يمكن وضع الوظيفة في نص برمجي على النحو التالي:

#!/usr/bin/env bash

split() {
    # ...
}

split "[email protected]"

دون تحديد IFS

إذا كان لديك نقطتان واحدتان فقط ، فيمكنك القيام بذلك:

a="foo:bar"
b=${a%:*}
c=${a##*:}

ستحصل:

b = foo
c = bar

في Android shell ، لا تعمل معظم الطرق المقترحة فقط:

$ IFS=':' read -ra ADDR <<<"$PATH"                             
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory

ما العمل هو:

$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin

حيث // يعني الاستبدال العالمي.


لقد رأيت بضعة إجابات تشير إلى أمر cut ، ولكن تم حذفها كلها. من الغريب أن لا أحد قد شرح ذلك ، لأنني أعتقد أنه واحد من أكثر الأوامر المفيدة للقيام بهذا النوع من الأشياء ، وخاصة لتحليل ملفات السجل المحددة.

في حالة تقسيم هذا المثال المحدد إلى مصفوفة سيناريو bash ، من المحتمل أن يكون tr أكثر كفاءة ، ولكن يمكن استخدام cut ، ويكون أكثر فعالية إذا كنت ترغب في سحب حقول محددة من الوسط.

مثال:

$ echo "[email protected];[email protected]" | cut -d ";" -f 1
[email protected]
$ echo "[email protected];[email protected]" | cut -d ";" -f 2
[email protected]

يمكنك بوضوح وضع ذلك في حلقة ، وتكرار المعلمة -f لسحب كل حقل بشكل مستقل.

يصبح هذا أكثر فائدة عندما يكون لديك ملف سجل محدد بصفوف مثل هذا:

2015-04-27|12345|some action|an attribute|meta data

cut سهل جدا لتكون قادرة على cat هذا الملف واختيار حقل معين لمزيد من المعالجة.


مأخوذة من صفيف تقسيم باش شل :

IN="[email protected];[email protected]"
arrIN=(${IN//;/ })

تفسير:

هذا البناء يستبدل كل تكرارات ';' (تعني القيمة الأولية لـ "الاستبدال العام") في السلسلة IN مع ' ' (مساحة واحدة) ، ثم تفسر السلسلة المفصولة بفراغ كمصفوفة (وهذا ما تفعله الأقواس المحيطة).

بناء الجملة المستخدم داخل الأقواس المتعرجة ليحل محل كل ';' تُسمى الشخصية ذات الحرف ' ' Parameter Expansion" .

هناك بعض المشاع الشائعة:

  1. إذا كانت السلسلة الأصلية تحتوي على مسافات ، فستحتاج إلى استخدام IFS :
    • IFS=':'; arrIN=($IN); unset IFS;
  2. إذا كانت السلسلة الأصلية تحتوي على مسافات وكان المحدد خطًا جديدًا ، فيمكنك تعيين IFS مع:
    • IFS=$'\n'; arrIN=($IN); unset IFS;

ماذا عن هذه البطانة ، إذا كنت لا تستخدم المصفوفات:

IFS=';' read ADDR1 ADDR2 <<<$IN

هذا العمل بالنسبة لي:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2

هذه هي أبسط طريقة للقيام بذلك.

spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}

هنا هو نظيفة 3 الخطوط الملاحية المنتظمة:

in="[email protected];[email protected];[email protected];[email protected]"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

حيث يقوم IFS بتحديد الكلمات استنادًا إلى الفاصل و () يتم استخدامه لإنشاء array . ثم يتم استخدام [@] لإرجاع كل عنصر ككلمة منفصلة.

إذا كان لديك أي كود بعد ذلك ، فستحتاج أيضًا إلى استعادة $IFS ، على سبيل المثال ، unset IFS .


هناك بعض الإجابات الرائعة هنا (errator esp.) ، ولكن لشيء مشابه للانقسام في اللغات الأخرى - وهو ما أخذته السؤال الأصلي ليعني - استقرت على هذا:

IN="[email protected];[email protected]"
declare -a a="(${IN/;/ })";

الآن ${a[0]} ، ${a[1]} ، إلخ ، هي كما تتوقع. استخدم ${#a[*]} لعدد من المصطلحات. أو للتكرار ، بالطبع:

for i in ${a[*]}; do echo $i; done

ملاحظة مهمة:

هذا يعمل في الحالات التي لا توجد فيها مسافات للقلق ، والتي تحل مشكلتي ، ولكنها قد لا تحل مشكلتك. الذهاب مع حل (حلول) $IFS في هذه الحالة.


هناك طريقة بسيطة وذكية مثل هذا:

echo "add:sfff" | xargs -d: -i  echo {}

ولكن يجب عليك استخدام gnu xargs، BSD xargs cant support -d delim. إذا كنت تستخدم أبل ماك مثلي. يمكنك تثبيت gnu xargs:

brew install findutils

ثم

echo "add:sfff" | gxargs -d: -i  echo {}

Maybe not the most elegant solution, but works with * and spaces:

IN="[email protected] me.com;*;[email protected]"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
   echo "> [`echo $IN | cut -d';' -f$i`]"
done

Outputs

> [[email protected] me.com]
> [*]
> [[email protected]]

Other example (delimiters at beginning and end):

IN=";[email protected] me.com;*;[email protected];"
> []
> [[email protected] me.com]
> [*]
> [[email protected]]
> []

Basically it removes every character other than ; making delims eg. ;;; . Then it does for loop from 1 to number-of-delimiters as counted by ${#delims} . The final step is to safely get the $i th part using cut .


Okay guys!

Here's my answer!

DELIMITER_VAL='='

read -d '' F_ABOUT_DISTRO_R <<"EOF"
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
NAME="Ubuntu"
VERSION="14.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.4 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
EOF

SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
while read -r line; do
   SPLIT+=("$line")
done <<< "$SPLIT_NOW"
for i in "${SPLIT[@]}"; do
    echo "$i"
done

Why this approach is "the best" for me?

Because of two reasons:

  1. You do not need to escape the delimiter;
  2. You will not have problem with blank spaces . The value will be properly separated in the array!

[]'s



IN='[email protected];[email protected];Charlie Brown <[email protected];!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f

انتاج:

[email protected]
[email protected]
Charlie Brown <[email protected]
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

Explanation: التعيين البسيط باستخدام الأقواس () يحول قائمة مفصولة مفصولة إلى مصفوفة بشرط أن يكون لديك IFS صحيح أثناء القيام بذلك. تعيّن حلقة FOR القياسية عناصر فردية في ذلك الصفيف كالمعتاد. لاحظ أن القائمة المعطاة لمتغير IN يجب أن تكون "صلبة" ، أي ، مع علامات التجزئة المفردة.

يجب أن يتم حفظ IFS واستعادتها نظرًا لأن Bash لا يتعامل مع الواجب بنفس طريقة الأمر. حل بديل هو التفاف المهمة داخل دالة واستدعاء تلك الوظيفة مع IFS معدلة. في هذه الحالة ، لا توجد حاجة إلى توفير / استعادة IFS منفصلة. شكرا ل "Bize" للإشارة إلى ذلك.





scripting